├── .gitignore ├── .gitmodules ├── Makefile ├── Makefile.deps ├── Makefile.targ ├── README.md ├── agent.js ├── agent.json ├── docs ├── agent.restdown ├── index.restdown ├── media │ └── img │ │ ├── favicon.ico │ │ └── logo.png ├── mib.restdown ├── protocol.restdown ├── provider.restdown └── snmp.restdown ├── lib ├── agent.js ├── client.js ├── errors │ ├── message.js │ └── varbind.js ├── index.js ├── lexer.js ├── listener.js ├── mib.js ├── mib │ ├── index.js │ └── mib-2 │ │ └── system.js ├── protocol │ ├── data.js │ ├── message.js │ ├── pdu.js │ ├── uint64_t.js │ └── varbind.js ├── provider.js ├── receiver.js ├── snmp.jison └── trap_listener.js ├── package.json ├── smf └── manifests │ └── snmpd.xml ├── snmpbulkget.js ├── snmpget.js ├── snmpinform.js ├── snmpset.js ├── snmptrap.js ├── snmpwalk.js ├── test └── protocol │ ├── data.test.js │ └── uint64_t.test.js ├── tl.js ├── tl.json └── tools ├── jsl.node.conf └── service_bundle.dtd.1 /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib/parser.js 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/javascriptlint"] 2 | path = deps/javascriptlint 3 | url = git://github.com/davepacheco/javascriptlint.git 4 | [submodule "deps/jsstyle"] 5 | path = deps/jsstyle 6 | url = git://github.com/davepacheco/jsstyle.git 7 | [submodule "deps/restdown"] 8 | path = deps/restdown 9 | url = git://github.com/trentm/restdown.git 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012, Joyent, Inc. All rights reserved. 3 | # 4 | # Makefile: basic Makefile for template API service 5 | # 6 | # This Makefile is a template for new repos. It contains only repo-specific 7 | # logic and uses included makefiles to supply common targets (javascriptlint, 8 | # jsstyle, restdown, etc.), which are used by other repos as well. You may well 9 | # need to rewrite most of this file, but you shouldn't need to touch the 10 | # included makefiles. 11 | # 12 | # If you find yourself adding support for new targets that could be useful for 13 | # other projects too, you should add these to the original versions of the 14 | # included Makefiles (in eng.git) so that other teams can use them too. 15 | # 16 | 17 | # 18 | # Tools 19 | # 20 | NPM := npm 21 | TAP := ./node_modules/.bin/tap 22 | JISON := ./node_modules/.bin/jison 23 | 24 | # 25 | # Files 26 | # 27 | DOC_FILES = \ 28 | agent.restdown \ 29 | index.restdown \ 30 | mib.restdown \ 31 | protocol.restdown \ 32 | provider.restdown \ 33 | snmp.restdown 34 | 35 | JS_FILES := \ 36 | snmpbulkget.js \ 37 | snmpget.js \ 38 | snmpset.js \ 39 | snmptrap.js \ 40 | snmpwalk.js \ 41 | tl.js \ 42 | agent.js \ 43 | lib/agent.js \ 44 | lib/client.js \ 45 | lib/index.js \ 46 | lib/lexer.js \ 47 | lib/errors/message.js \ 48 | lib/errors/varbind.js \ 49 | lib/listener.js \ 50 | lib/mib/index.js \ 51 | lib/mib/mib-2/system.js \ 52 | lib/mib.js \ 53 | lib/protocol/data.js \ 54 | lib/protocol/message.js \ 55 | lib/protocol/pdu.js \ 56 | lib/protocol/uint64_t.js \ 57 | lib/protocol/varbind.js \ 58 | lib/receiver.js \ 59 | lib/trap_listener.js \ 60 | lib/provider.js 61 | 62 | JSL_CONF_NODE = tools/jsl.node.conf 63 | JSL_FILES_NODE = $(JS_FILES) 64 | JSSTYLE_FILES = $(JS_FILES) 65 | JSSTYLE_FLAGS = -o indent=tab,doxygen,unparenthesized-return=1 66 | SMF_MANIFESTS = smf/manifests/snmpd.xml 67 | 68 | CLEAN_FILES += lib/parser.js 69 | 70 | # 71 | # Repo-specific targets 72 | # 73 | .PHONY: all 74 | all: rebuild lib/parser.js 75 | 76 | .PHONY: rebuild 77 | rebuild: 78 | $(NPM) rebuild 79 | 80 | .PHONY: test 81 | test: $(TAP) 82 | TAP=1 $(TAP) test 83 | 84 | lib/parser.js: lib/snmp.jison rebuild 85 | $(JISON) -o $@ $< 86 | 87 | include ./Makefile.deps 88 | include ./Makefile.targ 89 | -------------------------------------------------------------------------------- /Makefile.deps: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.deps: Makefile for including common tools as dependencies 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This file is separate from Makefile.targ so that teams can choose 21 | # independently whether to use the common targets in Makefile.targ and the 22 | # common tools here. 23 | # 24 | 25 | # 26 | # javascriptlint 27 | # 28 | JSL_EXEC ?= deps/javascriptlint/build/install/jsl 29 | JSL ?= $(JSL_EXEC) 30 | 31 | $(JSL_EXEC): | deps/javascriptlint/.git 32 | cd deps/javascriptlint && make install 33 | 34 | distclean:: 35 | if [[ -f deps/javascriptlint/Makefile ]]; then \ 36 | cd deps/javascriptlint && make clean; \ 37 | fi 38 | 39 | # 40 | # jsstyle 41 | # 42 | JSSTYLE_EXEC ?= deps/jsstyle/jsstyle 43 | JSSTYLE ?= $(JSSTYLE_EXEC) 44 | 45 | $(JSSTYLE_EXEC): | deps/jsstyle/.git 46 | 47 | # 48 | # restdown 49 | # 50 | RESTDOWN_EXEC ?= deps/restdown/bin/restdown 51 | RESTDOWN ?= python $(RESTDOWN_EXEC) 52 | $(RESTDOWN_EXEC): | deps/restdown/.git 53 | 54 | EXTRA_DOC_DEPS ?= 55 | -------------------------------------------------------------------------------- /Makefile.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.targ: common targets. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This Makefile defines several useful targets and rules. You can use it by 21 | # including it from a Makefile that specifies some of the variables below. 22 | # 23 | # Targets defined in this Makefile: 24 | # 25 | # check Checks JavaScript files for lint and style 26 | # Checks bash scripts for syntax 27 | # Checks SMF manifests for validity against the SMF DTD 28 | # 29 | # clean Removes built files 30 | # 31 | # docs Builds restdown documentation in docs/ 32 | # 33 | # prepush Depends on "check" and "test" 34 | # 35 | # test Does nothing (you should override this) 36 | # 37 | # xref Generates cscope (source cross-reference index) 38 | # 39 | # For details on what these targets are supposed to do, see the Joyent 40 | # Engineering Guide. 41 | # 42 | # To make use of these targets, you'll need to set some of these variables. Any 43 | # variables left unset will simply not be used. 44 | # 45 | # BASH_FILES Bash scripts to check for syntax 46 | # (paths relative to top-level Makefile) 47 | # 48 | # CLEAN_FILES Files to remove as part of the "clean" target. Note 49 | # that files generated by targets in this Makefile are 50 | # automatically included in CLEAN_FILES. These include 51 | # restdown-generated HTML and JSON files. 52 | # 53 | # DOC_FILES Restdown (documentation source) files. These are 54 | # assumed to be contained in "docs/", and must NOT 55 | # contain the "docs/" prefix. 56 | # 57 | # JSL_CONF_NODE Specify JavaScriptLint configuration files 58 | # JSL_CONF_WEB (paths relative to top-level Makefile) 59 | # 60 | # Node.js and Web configuration files are separate 61 | # because you'll usually want different global variable 62 | # configurations. If no file is specified, none is given 63 | # to jsl, which causes it to use a default configuration, 64 | # which probably isn't what you want. 65 | # 66 | # JSL_FILES_NODE JavaScript files to check with Node config file. 67 | # JSL_FILES_WEB JavaScript files to check with Web config file. 68 | # 69 | # JSON_FILES JSON files to be validated 70 | # 71 | # JSSTYLE_FILES JavaScript files to be style-checked 72 | # 73 | # You can also override these variables: 74 | # 75 | # BASH Path to bash (default: "bash") 76 | # 77 | # CSCOPE_DIRS Directories to search for source files for the cscope 78 | # index. (default: ".") 79 | # 80 | # JSL Path to JavaScriptLint (default: "jsl") 81 | # 82 | # JSL_FLAGS_NODE Additional flags to pass through to JSL 83 | # JSL_FLAGS_WEB 84 | # JSL_FLAGS 85 | # 86 | # JSON Path to json tool (default: "json") 87 | # 88 | # JSSTYLE Path to jsstyle (default: "jsstyle") 89 | # 90 | # JSSTYLE_FLAGS Additional flags to pass through to jsstyle 91 | # 92 | # RESTDOWN_EXT By default '.md' is required for DOC_FILES (see above). 93 | # If you want to use, say, '.restdown' instead, then set 94 | # 'RESTDOWN_EXT=.restdown' in your Makefile. 95 | # 96 | 97 | # 98 | # Defaults for the various tools we use. 99 | # 100 | BASH ?= bash 101 | BASHSTYLE ?= tools/bashstyle 102 | CP ?= cp 103 | CSCOPE ?= cscope 104 | CSCOPE_DIRS ?= . 105 | JSL ?= jsl 106 | JSON ?= json 107 | JSSTYLE ?= jsstyle 108 | MKDIR ?= mkdir -p 109 | MV ?= mv 110 | RESTDOWN_FLAGS ?= 111 | RESTDOWN_EXT ?= .md 112 | RMTREE ?= rm -rf 113 | JSL_FLAGS ?= --nologo --nosummary 114 | 115 | ifeq ($(shell uname -s),SunOS) 116 | TAR ?= gtar 117 | else 118 | TAR ?= tar 119 | endif 120 | 121 | 122 | # 123 | # Defaults for other fixed values. 124 | # 125 | BUILD = build 126 | DISTCLEAN_FILES += $(BUILD) 127 | DOC_BUILD = $(BUILD)/docs/public 128 | 129 | # 130 | # Configure JSL_FLAGS_{NODE,WEB} based on JSL_CONF_{NODE,WEB}. 131 | # 132 | ifneq ($(origin JSL_CONF_NODE), undefined) 133 | JSL_FLAGS_NODE += --conf=$(JSL_CONF_NODE) 134 | endif 135 | 136 | ifneq ($(origin JSL_CONF_WEB), undefined) 137 | JSL_FLAGS_WEB += --conf=$(JSL_CONF_WEB) 138 | endif 139 | 140 | # 141 | # Targets. For descriptions on what these are supposed to do, see the 142 | # Joyent Engineering Guide. 143 | # 144 | 145 | # 146 | # Instruct make to keep around temporary files. We have rules below that 147 | # automatically update git submodules as needed, but they employ a deps/*/.git 148 | # temporary file. Without this directive, make tries to remove these .git 149 | # directories after the build has completed. 150 | # 151 | .SECONDARY: $($(wildcard deps/*):%=%/.git) 152 | 153 | # 154 | # This rule enables other rules that use files from a git submodule to have 155 | # those files depend on deps/module/.git and have "make" automatically check 156 | # out the submodule as needed. 157 | # 158 | deps/%/.git: 159 | git submodule update --init deps/$* 160 | 161 | # 162 | # These recipes make heavy use of dynamically-created phony targets. The parent 163 | # Makefile defines a list of input files like BASH_FILES. We then say that each 164 | # of these files depends on a fake target called filename.bashchk, and then we 165 | # define a pattern rule for those targets that runs bash in check-syntax-only 166 | # mode. This mechanism has the nice properties that if you specify zero files, 167 | # the rule becomes a noop (unlike a single rule to check all bash files, which 168 | # would invoke bash with zero files), and you can check individual files from 169 | # the command line with "make filename.bashchk". 170 | # 171 | .PHONY: check-bash 172 | check-bash: $(BASH_FILES:%=%.bashchk) $(BASH_FILES:%=%.bashstyle) 173 | 174 | %.bashchk: % 175 | $(BASH) -n $^ 176 | 177 | %.bashstyle: % 178 | $(BASHSTYLE) $^ 179 | 180 | .PHONY: check-json 181 | check-json: $(JSON_FILES:%=%.jsonchk) 182 | 183 | %.jsonchk: % 184 | $(JSON) --validate -f $^ 185 | 186 | # 187 | # The above approach can be slow when there are many files to check because it 188 | # requires that "make" invoke the check tool once for each file, rather than 189 | # passing in several files at once. For the JavaScript check targets, we define 190 | # a variable for the target itself *only if* the list of input files is 191 | # non-empty. This avoids invoking the tool if there are no files to check. 192 | # 193 | JSL_NODE_TARGET = $(if $(JSL_FILES_NODE), check-jsl-node) 194 | .PHONY: check-jsl-node 195 | check-jsl-node: $(JSL_EXEC) 196 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_NODE) $(JSL_FILES_NODE) 197 | 198 | JSL_WEB_TARGET = $(if $(JSL_FILES_WEB), check-jsl-web) 199 | .PHONY: check-jsl-web 200 | check-jsl-web: $(JSL_EXEC) 201 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_WEB) $(JSL_FILES_WEB) 202 | 203 | .PHONY: check-jsl 204 | check-jsl: $(JSL_NODE_TARGET) $(JSL_WEB_TARGET) 205 | 206 | JSSTYLE_TARGET = $(if $(JSSTYLE_FILES), check-jsstyle) 207 | .PHONY: check-jsstyle 208 | check-jsstyle: $(JSSTYLE_EXEC) 209 | $(JSSTYLE) $(JSSTYLE_FLAGS) $(JSSTYLE_FILES) 210 | 211 | .PHONY: check 212 | check:: check-jsl check-json $(JSSTYLE_TARGET) check-bash 213 | @echo check ok 214 | 215 | .PHONY: clean 216 | clean:: 217 | -$(RMTREE) $(CLEAN_FILES) 218 | 219 | .PHONY: distclean 220 | distclean:: clean 221 | -$(RMTREE) $(DISTCLEAN_FILES) 222 | 223 | CSCOPE_FILES = cscope.in.out cscope.out cscope.po.out 224 | CLEAN_FILES += $(CSCOPE_FILES) 225 | 226 | .PHONY: xref 227 | xref: cscope.files 228 | $(CSCOPE) -bqR 229 | 230 | .PHONY: cscope.files 231 | cscope.files: 232 | find $(CSCOPE_DIRS) -name '*.c' -o -name '*.h' -o -name '*.cc' \ 233 | -o -name '*.js' -o -name '*.s' -o -name '*.cpp' > $@ 234 | 235 | # 236 | # The "docs" target is complicated because we do several things here: 237 | # 238 | # (1) Use restdown to build HTML and JSON files from each of DOC_FILES. 239 | # 240 | # (2) Copy these files into $(DOC_BUILD) (build/docs/public), which 241 | # functions as a complete copy of the documentation that could be 242 | # mirrored or served over HTTP. 243 | # 244 | # (3) Then copy any directories and media from docs/media into 245 | # $(DOC_BUILD)/media. This allows projects to include their own media, 246 | # including files that will override same-named files provided by 247 | # restdown. 248 | # 249 | # Step (3) is the surprisingly complex part: in order to do this, we need to 250 | # identify the subdirectories in docs/media, recreate them in 251 | # $(DOC_BUILD)/media, then do the same with the files. 252 | # 253 | DOC_MEDIA_DIRS := $(shell find docs/media -type d 2>/dev/null | grep -v "^docs/media$$") 254 | DOC_MEDIA_DIRS := $(DOC_MEDIA_DIRS:docs/media/%=%) 255 | DOC_MEDIA_DIRS_BUILD := $(DOC_MEDIA_DIRS:%=$(DOC_BUILD)/media/%) 256 | 257 | DOC_MEDIA_FILES := $(shell find docs/media -type f 2>/dev/null) 258 | DOC_MEDIA_FILES := $(DOC_MEDIA_FILES:docs/media/%=%) 259 | DOC_MEDIA_FILES_BUILD := $(DOC_MEDIA_FILES:%=$(DOC_BUILD)/media/%) 260 | 261 | # 262 | # Like the other targets, "docs" just depends on the final files we want to 263 | # create in $(DOC_BUILD), leveraging other targets and recipes to define how 264 | # to get there. 265 | # 266 | .PHONY: docs 267 | docs: \ 268 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.html) \ 269 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.json) \ 270 | $(DOC_MEDIA_FILES_BUILD) 271 | 272 | # 273 | # We keep the intermediate files so that the next build can see whether the 274 | # files in DOC_BUILD are up to date. 275 | # 276 | .PRECIOUS: \ 277 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 278 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%json) 279 | 280 | # 281 | # We do clean those intermediate files, as well as all of DOC_BUILD. 282 | # 283 | CLEAN_FILES += \ 284 | $(DOC_BUILD) \ 285 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 286 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.json) 287 | 288 | # 289 | # Before installing the files, we must make sure the directories exist. The | 290 | # syntax tells make that the dependency need only exist, not be up to date. 291 | # Otherwise, it might try to rebuild spuriously because the directory itself 292 | # appears out of date. 293 | # 294 | $(DOC_MEDIA_FILES_BUILD): | $(DOC_MEDIA_DIRS_BUILD) 295 | 296 | $(DOC_BUILD)/%: docs/% | $(DOC_BUILD) 297 | $(CP) $< $@ 298 | 299 | docs/%.json docs/%.html: docs/%$(RESTDOWN_EXT) | $(DOC_BUILD) $(RESTDOWN_EXEC) \ 300 | $(EXTRA_DOC_DEPS) 301 | $(RESTDOWN) $(RESTDOWN_FLAGS) -m $(DOC_BUILD) $< 302 | 303 | $(DOC_BUILD): 304 | $(MKDIR) $@ 305 | 306 | $(DOC_MEDIA_DIRS_BUILD): 307 | $(MKDIR) $@ 308 | 309 | # 310 | # The default "test" target does nothing. This should usually be overridden by 311 | # the parent Makefile. It's included here so we can define "prepush" without 312 | # requiring the repo to define "test". 313 | # 314 | .PHONY: test 315 | test: 316 | 317 | .PHONY: prepush 318 | prepush: check test 319 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | snmpjs provides a toolkit for SNMP agents and management applications in 2 | Node.js. 3 | 4 | ## Usage 5 | 6 | For full docs, see . 7 | 8 | var os = require('os'); 9 | var snmp = require('snmpjs'); 10 | 11 | var agent = snmp.createAgent(); 12 | 13 | agent.request({ oid: '.1.3.6.1.2.1.1.5', handler: function (prq) { 14 | var nodename = os.hostname(); 15 | var val = snmp.data.createData({ type: 'OctetString', 16 | value: nodename }); 17 | 18 | snmp.provider.readOnlyScalar(prq, val); 19 | } }); 20 | 21 | agent.bind({ family: 'udp4', port: 161 }); 22 | 23 | Try hitting that with your favourite SNMP get utility, such as: 24 | 25 | $ snmpget -v 2c -c any localhost .1.3.6.1.2.1.1.5.0 26 | 27 | ## Installation 28 | 29 | $ npm install snmpjs 30 | 31 | ## License 32 | 33 | MIT. 34 | 35 | ## Bugs 36 | 37 | See . 38 | -------------------------------------------------------------------------------- /agent.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* 3 | * Copyright (c) 2012, Joyent, Inc. All rights reserved. 4 | */ 5 | 6 | var snmp = require('./lib/index.js'); 7 | var mib = require('./lib/mib/index.js'); 8 | var bunyan = require('bunyan'); 9 | var fs = require('fs'); 10 | 11 | var config = process.argv[2] || 'agent.json'; 12 | var cfstr = fs.readFileSync(config); 13 | var cf, log_cf; 14 | var log, agent; 15 | 16 | cf = JSON.parse(cfstr); 17 | log_cf = cf.log || { 18 | name: 'snmpd', 19 | level: 'trace' 20 | }; 21 | 22 | log = new bunyan(log_cf); 23 | 24 | agent = snmp.createAgent({ 25 | log: log 26 | }); 27 | 28 | /* XXX MIB configuration */ 29 | 30 | agent.request(mib); 31 | 32 | agent.bind({ family: 'udp4', port: 161 }); 33 | -------------------------------------------------------------------------------- /agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "name": "snmpd", 4 | "level": "trace" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/agent.restdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Agent API | snmpjs 3 | markdown2extras: wiki-tables 4 | --- 5 | 6 | # snmpjs Agent API 7 | 8 | Agents are software components that make data available to management 9 | applications via SNMP. They are analogous to HTTP servers. With snmpjs, it is 10 | as easy to implement an SNMP agent as it is to implement an HTTP server using 11 | built-in [Node.js][] functionality. The agent framework provides only a few 12 | interfaces, which are described in the remainder of this document. 13 | 14 | # createAgent(options) 15 | 16 | This function creates and returns an Agent object. Its sole argument, which is 17 | optional, contains any or all of the following members: 18 | 19 | { 20 | name: [String], 21 | dtrace: [Object (DTraceProvider)], 22 | log: [Object (bunyan)] 23 | } 24 | 25 | The interfaces provided by the Agent object are described in detail below. 26 | 27 | ### name 28 | 29 | If present, the `name` member will be used to name the DTrace provider and to 30 | decorate log output. The default value is `'snmpjs'`. 31 | 32 | ### dtrace 33 | 34 | If present, the agent's DTrace probes will be appended to the existing provider 35 | referenced by the `dtrace` member. Otherwise, a new provider identified by the 36 | `name` member (or its default value if absent) will be created. 37 | 38 | ### log 39 | 40 | If present, the agent will add its own child logger to the existing [Bunyan][] 41 | logger referenced by the `log` member. Otherwise, a new logger identified by 42 | the `name` member (or its default value if absent) will be created. 43 | 44 | # Agent 45 | 46 | The Agent object, created by a call to `createAgent`, provides interfaces for 47 | defining where the agent listens for requests and what it should do with them. 48 | An agent will not receive any requests until it is bound to an endpoint that 49 | generates them, and it will respond with errors to all requests if no providers 50 | have been loaded. 51 | 52 | ## Agent.request(prov) 53 | 54 | This method attaches one or more MIB providers to the agent, allowing it to 55 | usefully handle requests. The sole argument must be either a provider 56 | descriptor as specified by the [provider API][], or an array of such objects. 57 | 58 | ## Agent.bind(endpoint, cb) 59 | 60 | Analogous to the `dgram.bind()` method, this method causes the agent to bind a 61 | newly-created socket to the specified IPv4 or IPv6 UDP port and begin receiving 62 | messages. The endpoint argument must be an object with the following members: 63 | 64 | { 65 | family: [String], 66 | port: [Number], 67 | [ addr: [String] ] 68 | } 69 | 70 | The `family` member must be a string acceptable to `dgram.createSocket()`. The 71 | `port` member must be an integer in \[1, 65535\], and otherwise acceptable to 72 | `dgram.bind()`. If present, the optional `addr` member will be used to restrict 73 | the IPv4 or IPv6 address on which the agent will receive messages; by default, 74 | it will receive messages on all addresses of the specified address family. 75 | 76 | If provided, the optional argument cb must be a function; it will be invoked 77 | when the 'listening' event is emitted by the underlying socket. 78 | 79 | This method may be called multiple times, such that it is possible to receive 80 | incoming messages on any number of endpoints. 81 | 82 | ## Agent.close() 83 | 84 | Analogous to the `dgram.close()` method, this method causes the agent to cease 85 | receiving incoming requests on all previously-bound endpoints. It takes no 86 | arguments. 87 | 88 | --- 89 | [provider API]: provider.html 90 | [Node.js]: http://nodejs.org 91 | [Bunyan]: https://github.com/trentm/node-bunyan 92 | -------------------------------------------------------------------------------- /docs/index.restdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: snmpjs 3 | markdown2extras: wiki-tables, code-friendly 4 | apisections: 5 | --- 6 | 7 |
8 | An SNMP for 9 | Node.js 10 |
11 | 12 | # Overview 13 | 14 | snmpjs is a pure JavaScript, from-scratch framework for implementing [SNMP][] 15 | agents, trap generators, and management applications in [Node.js][]. It is 16 | intended primarily to enable the construction of a simple, general-purpose agent 17 | as an alternative to Net-SNMP. 18 | 19 | var os = require('os'); 20 | var snmp = require('snmpjs'); 21 | var logger = require('bunyan'); 22 | 23 | var log = new logger({ 24 | name: 'snmpd', 25 | level: 'info' 26 | }); 27 | 28 | var agent = snmp.createAgent({ 29 | log: log 30 | }); 31 | 32 | agent.request({ oid: '.1.3.6.1.2.1.1.5', handler: function (prq) { 33 | var nodename = os.hostname(); 34 | var val = snmp.data.createData({ type: 'OctetString', 35 | value: nodename }); 36 | 37 | snmp.provider.readOnlyScalar(prq, val); 38 | } }); 39 | 40 | agent.bind({ family: 'udp4', port: 161 }); 41 | 42 | Try hitting that with your favourite SNMP get utility, such as: 43 | 44 | $ snmpget -v 2c -c any localhost .1.3.6.1.2.1.1.5 45 | 46 | # Features 47 | 48 | snmpjs allows you to implement agents, trap generators, and management 49 | applications that support the full range of SNMPv1 and SNMPv2c functionality. 50 | It is mostly compatible with all relevant standards -- see notes below -- and 51 | can also be extended to handle (and, optionally, to generate) SNMP messages that 52 | incorporate nonstandard data types and other deviations. Several pieces of 53 | standard MIB functionality are included along with a simple agent, but you can 54 | easily implement your own MIB subtrees or alternative agent. The library can 55 | also be used to incorporate SNMP functionality into other daemons; for example, 56 | to allow them to generate traps if a fault condition is detected. 57 | 58 | # Getting Started 59 | 60 | $ npm install snmpjs 61 | 62 | If you're new to SNMP, check out the [gentle introduction][]. Otherwise, the 63 | API documentation is: 64 | 65 | ||[agent][]||Reference for implementing SNMP agents.|| 66 | ||[mib][]||API reference for the Management Information Base (MIB).|| 67 | ||[protocol][]||API reference for low-level message encoding and decoding.|| 68 | ||[provider][]||API reference for MIB providers.|| 69 | 70 | # What's Not Included 71 | 72 | Many pieces of functionality that would be useful are missing. The most 73 | noteworthy, some of which are really needed in order to create a complete suite 74 | of standard MIB providers, include: 75 | 76 | - Agent: handling GetBulkRequests (*) 77 | - Agent: error responses in the face of unparseable messages 78 | - Agent: handling of excessively large responses (i.e., tooBig) 79 | - Agent: access control; there is none at all presently (*) 80 | - Agent: AgentX support, for aggregating disjoint MIBs on the same host and 81 | allowing multiple daemons to provide their own MIB subtrees 82 | - Agent: introspection; i.e., an interface for providers to access information 83 | about the agent itself 84 | - Agent: provider configuration; i.e., an interface for making agent 85 | configuration data available to MIB providers 86 | - Other: More standard MIB providers 87 | - Other: convenience functions for generating traps 88 | - Management: command-line utilities for sending requests and displaying 89 | responses 90 | - MIB: a MIB definition parser (to decorate the MIB for the benefit of 91 | management applications or for strict checking of provider responses) 92 | - Provider: Convenience functions for implementing tabular providers; e.g., 93 | lexicographically-sorting data structures 94 | - All: SNMPv3 support 95 | 96 | (*) These items are really bugs that have practical solutions; they will be 97 | addressed in subsequent revisions of the software. 98 | 99 | # Conformance 100 | 101 | In general, deviations from the relevant specifications are considered bugs and 102 | should have issues opened to track them. However, there is one notable 103 | exception, in which the design of snmpjs precludes an effective implementation 104 | of required behaviour. Specifically, SetRequests, clearly intended by the 105 | standard to be treated as an atomic transaction when multiple varbinds are 106 | present, are not handled that way. Instead, they are treated as multiple 107 | independent requests that can succeed or fail without affecting the others. If 108 | a failure occurs, the first varbind to cause a failure will be highlighted by 109 | the `error_index` field in the response, and the nature of the error will be 110 | specified by `error_status`. Subsequent varbinds may or may not have been 111 | assigned successfully. The `commitFailed` and `undoFailed` status codes are not 112 | used. This approach dramatically simplifies the implementation (the standard 113 | disdainfully yet correctly calls this "taking the easy way out"), and practical 114 | use of SNMP for control operations where such transactionality is required is 115 | exceedingly rare. If you are unfortunate enough to need it, however, you will 116 | need to select an agent that is not based on snmpjs. 117 | 118 | # More Information 119 | 120 | ||License||[MIT][]|| 121 | ||Code||[joyent/node-snmpjs][]|| 122 | ||node.js version||0.6.x|| 123 | 124 | --- 125 | [SNMP]: http://tools.ietf.org/html/rfc3411 126 | [Node.js]: http://nodejs.org 127 | [gentle introduction]: snmp.html 128 | [agent]: agent.html 129 | [mib]: mib.html 130 | [protocol]: protocol.html 131 | [provider]: provider.html 132 | [joyent/node-snmpjs]: https://github.com/joyent/node-snmpjs 133 | [MIT]: http://www.opensource.org/licenses/mit-license.php 134 | -------------------------------------------------------------------------------- /docs/media/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TritonDataCenter/node-snmpjs/c4a77ef8556c47bfd35cb02a5c9d4876c117d99f/docs/media/img/favicon.ico -------------------------------------------------------------------------------- /docs/media/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TritonDataCenter/node-snmpjs/c4a77ef8556c47bfd35cb02a5c9d4876c117d99f/docs/media/img/logo.png -------------------------------------------------------------------------------- /docs/mib.restdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: MIB API | snmpjs 3 | markdown2extras: wiki-tables 4 | --- 5 | 6 | # snmpjs MIB API 7 | 8 | The MIB is the key-value database on which an SNMP agent operates on behalf of 9 | management applications. A MIB contains information about the set of object 10 | types and identifiers known to the SNMP entity. The agent uses this to store 11 | information about how to act on requests that it received; a management 12 | application may wish to store information about how to display data items 13 | received from an agent, how to validate the format of values being sent to an 14 | agent, or how to translate between numeric object identifiers and the 15 | human-readable text that names those objects. The MIB functionality in snmpjs 16 | can be used for any of these purposes, though the implementation provides only 17 | basic functionality and leaves the details of these activities to consumers. 18 | 19 | ## createMIB(def) 20 | 21 | # MIB 22 | 23 | A MIB object, created by a call to `snmp.createMIB`, provides simple interfaces 24 | for adding and searching for SNMP objects within the MIB. 25 | 26 | ## MIB.add(def) 27 | 28 | This method adds a node, and any previously missing ancestor nodes, to the MIB. 29 | Its sole argument, which is required, may be any of the following: an OID in 30 | string form, an OID in array form, or any object containing a member `oid` of 31 | either form. If an object is provided, only the `oid` member will be examined 32 | and the object will not be modified. 33 | 34 | ## MIB.lookup(oid) 35 | 36 | This method returns the `MIBNode`, if any, that either matches exactly the 37 | specified OID or, if there is no such node, is the nearest ancestor present in 38 | the MIB. The sole argument, which is required, must be an OID in either string 39 | or array form. The returned node may be inspected to determine whether an exact 40 | match was found in the MIB; see the `MIBNode` interfaces specified below. 41 | 42 | ## MIB.next_match(arg) 43 | 44 | This method iterates over the MIB or a portion thereof, in lexicographical order 45 | as defined by ASN.1, and returns the first node that satisfies caller-specified 46 | criteria. Its sole argument, which is required, must be an object with the 47 | following members: 48 | 49 | { 50 | node: [Object (MIBNode)], 51 | match: [Function], 52 | [ start: [Number] ] 53 | } 54 | 55 | The `node` specifies the starting point for the search. If the `start` member 56 | is present, only children whose first OID component *after* `node`'s name is at 57 | least `start` will be evaluated. For example, if `node` refers to a `MIBNode` 58 | whose `oid` member contains `'1.3.6.1'` and the `start` member contains `6`, the 59 | first node eligible for evaluate after `node` itself would have OID 1.3.6.1.6. 60 | Without specifying the `start` member, the first node eligible for evaluation 61 | after `node` itself would have OID 1.3.6.1.0; because that OID references an 62 | instance and the MIB does not contain instances, the first node that could 63 | actually be evaluated would have OID 1.3.6.1.1. 64 | 65 | The `match` member references a function used to evaluate nodes. Its sole 66 | argument is an object of type `MIBNode`. If it returns `true`, the search will 67 | stop and the most recently evaluated node will be returned by `next_match`. 68 | Otherwise, the search will continue. 69 | 70 | If the MIB is exhausted without satisfying the matching criteria, `null` is 71 | returned. 72 | 73 | # MIB Nodes 74 | 75 | The entries in the MIB are part of a tree, in which each subtree contains the 76 | portion of the OID space that shares a common prefix. The length of the path 77 | from a given node to the root of the tree is equal to the number of integer 78 | components in the node's OID. The MIB contains only object identifiers and 79 | object types; it does not contain any instances. Nodes in the MIB are 80 | represented by objects descended from `MIBNode`; objects of this type cannot be 81 | created directly but are created when new objects are added to the MIB. The 82 | remainder of this section describes their interfaces. 83 | 84 | ## MIBNode.oid 85 | 86 | This member contains the string form of the node's OID. 87 | 88 | ## MIBNode.addr 89 | 90 | This member contains an array of integers in \[0, 2^31 - 1\] representing the 91 | components of the node's OID. 92 | 93 | ## MIBNode.parent 94 | 95 | This member holds a reference to the node's parent node, which is either an 96 | object descended from `MIBNode` or `null` (if the node is the root node). 97 | 98 | ## MIBNode.child(idx) 99 | 100 | This method returns the child node whose last OID component is `idx`, which must 101 | be an integer in \[0, 2^31 - 1\]. If no such child exists, `null` is returned. 102 | 103 | ## MIBNode.listChildren(start) 104 | 105 | This method returns a dense array of integers, each of which is the index of a 106 | valid child of this node, such that it may be passed to `node.child()` and 107 | result in a non-`null` return value. If no matching children exist, an empty 108 | array is returned. 109 | 110 | The single argument, which is optional, is the lowest child index that should be 111 | included in the array. By default, all child indices are included. 112 | 113 | ## MIBNode.decorate(arg) 114 | 115 | MIB nodes may be decorated with consumer-specific data, which can later be 116 | retrieved when looking them up. Each consumer may add and retrieve its own 117 | decorations by using a unique tag. 118 | 119 | This method attaches a decorative object to a MIB node. Its sole argument, 120 | which is required, must be an object of the form: 121 | 122 | { 123 | tag: [String], 124 | obj: [Any] 125 | } 126 | 127 | The `obj` member's contents will be attached to the node as a decoration using 128 | the specified `tag`. As is customary throughout snmpjs, the portion of the tag 129 | namespace beginning with the `_` character is reserved for snmpjs's internal 130 | use, and must not be polluted by consumers. The effects of doing so are 131 | undefined. 132 | 133 | If a node has already been decorated with an object associated with the same tag 134 | that is specified in the argument, the previous decoration is replaced. 135 | 136 | ## MIBNode.decor(tag) 137 | 138 | This method returns the object or primitive that was previously associated with 139 | this node by a call to `MIBNode.decorate()`. The sole argument must be a string 140 | naming the tag under which the decoration was attached. If no decoration with 141 | the specified tag has been attached to the node, `undefined` is returned. 142 | 143 | ## MIBNode.isAncestor(oid) 144 | 145 | This method returns `true` if the node is an ancestor of the object or instance 146 | (which need not exist in the MIB) named by its sole argument, which may be an 147 | OID in string or array form. Otherwise it returns `false`. 148 | 149 | ## MIBNode.isDescended(oid) 150 | 151 | This method returns `true` if the node is a descendant of the object or instance 152 | (which need not exist in the MIB) named by its sole argument, which may be an 153 | OID in string or array form. Otherwise it returns false. Note that if the 154 | specified OID does not exist in the MIB, the node cannot possibly be its 155 | descendent, as the MIB always contains a node for every OID that is an ancestor 156 | of any object added to it. 157 | 158 | --- 159 | -------------------------------------------------------------------------------- /docs/provider.restdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: MIB Provider API | snmpjs 3 | markdown2extras: wiki-tables 4 | --- 5 | 6 | # snmpjs MIB Provider API 7 | 8 | MIB providers are software components that know how to handle and respond to 9 | requests for SNMP data within a portion of the MIB. Once a MIB provider has 10 | been registered with an agent, the agent will begin directing requests for the 11 | provider's MIB subtree to the provider's handler function(s), which are expected 12 | to notify the agent via a callback when they have determined the correct 13 | response to the request. A MIB provider has no knowledge of any part of the MIB 14 | other than the one for which it is responsible. 15 | 16 | # MIB Provider descriptors 17 | 18 | MIB provider descriptors are made available by providers for use by software 19 | incorporating SNMP agent functionality. Consumers collect and pass these 20 | descriptors to the `Agent.request` method to register them with the agent. Your 21 | provider module must therefore export its descriptor(s) and should document how 22 | they are structured. You are strongly encouraged to make your module's 23 | `exports` member a single Array object containing provider descriptors for each 24 | object your provider supports (see the mib-2 provider included with snmpjs for 25 | an example). 26 | 27 | A MIB provider descriptor is any object instance with the following members: 28 | 29 | { 30 | oid: [String], 31 | handler: [Function] or [Array of Function], 32 | [ columns: [Array of Number] ] 33 | } 34 | 35 | or a dense Array containing any number of such objects. 36 | 37 | Example: 38 | 39 | { 40 | oid: '.1.3.6.1.2.1.1.1', 41 | handler: _mib2_system_descr_handler 42 | } 43 | 44 | Members are described in detail as follows: 45 | 46 | ## oid 47 | 48 | The `oid` member must be present, and must be a string. It specifies the 49 | address within the MIB of either a scalar (singleton) object or an object 50 | representing a table entry type. It must not contain any components identifying 51 | an instance. For example: 52 | 53 | '.1.3.6.1.2.1.1.1' 54 | 55 | is the OID of `.iso.org.dod.internet.mgmt.mib-2.system.1` or `sysDescr`. It 56 | would be incorrect to specify this as `'.1.3.6.1.2.1.1.1.0'` because the latter 57 | OID contains the instance number of the (singleton) instance of this object 58 | within the MIB. 59 | 60 | Similarly, if the provider manages a table within the MIB, its oid should 61 | reflect the entry object type, not that of the table itself nor any of its 62 | columns or rows. For example: 63 | 64 | '.1.3.6.1.2.1.2.2.1' 65 | 66 | is the OID of `.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.1` or 67 | `ifEntry`. Do not specify any of the columns below this node in the MIB such as 68 | ifIndex (`.1.3.6.1.2.1.2.2.1.1`). 69 | 70 | ## handler 71 | 72 | The `handler` is a function or array of functions that will be invoked when a 73 | request is received that is within the provider's subtree. If this object is an 74 | array, each handler will be invoked in an arbitrary order. 75 | 76 | Each handler function will be invoked with a single argument of type 77 | `ProviderRequest`, described in detail below. 78 | 79 | ## columns 80 | 81 | The provider descriptor for a tabular object specifies the OID of the entry type 82 | in its `oid` member and a dense array of column numbers in the `columns` member. 83 | It is not required that the column identifier space be dense, nor is it required 84 | that the identifiers be ordered within the array. Each entry is a positive 85 | integer less than 2^31 specifying the last OID component of a column in the 86 | table entry type managed by this provider. For example, the `ifEntry` provider 87 | has columns numbered 1 through 22 inclusive, so the `columns` member of its 88 | provider definition would be a dense array of those integers. 89 | 90 | # Request Processing Overview 91 | 92 | When an agent receives a request that it associates with a MIB provider, it will 93 | invoke each of that provider's handler functions in succession. The handler(s) 94 | are responsible for performing the requested operation (if possible) and 95 | invoking a callback with the result, which may indicate that an error occurred. 96 | Exactly one of an object's handlers must invoke the completion callback; if 97 | multiple handlers invoke the completion callback, the result is undefined. This 98 | behaviour may change in a future API revision. 99 | 100 | Requests are directed to a provider as `ProviderRequest` instances, described in 101 | detail below. An agent will not direct a request to a provider that has not 102 | been registered with it, nor will it direct requests that are outside a 103 | provider's registered portion of the MIB. 104 | 105 | The `ProviderRequest` instance contains a complete description of the requested 106 | operation and a completion callback. There are four basic operation types that 107 | a provider may receive, which are defined by [RFC 3416][]. Because the language 108 | in the standard is somewhat complex, a simplified version is presented here with 109 | a focus on the facilities provided by snmpjs. Recall that there are two basic 110 | types of provider: one that handles scalar data items and one that handles 111 | tabular arrays of data items. We will examine the necessary processing for each 112 | in turn. There are also four request types, defined by relevant standards, that 113 | any provider must handle; each will be described here in brief. 114 | 115 | ### Operation Types 116 | 117 | The operation types are defined in [RFC 3416][] and, for convenience, have 118 | symbolic constants associated with them attached to the `snmp.pdu` object 119 | exported by snmpjs. They are: 120 | 121 | ||GetRequest||Read the single value specified by the request object|| 122 | ||SetRequest||Set the single value specified by the request object and value|| 123 | ||GetNextRequest||Read the single value FOLLOWING the request object|| 124 | ||GetBulkRequest||Read one or more values FOLLOWING the request object(s)|| 125 | 126 | The Get and Set requests do exactly what their names imply: retrieve or attempt 127 | to change a specific data value. 128 | 129 | The GetNext and GetBulk requests are used to walk the entire MIB or a subtree, 130 | by issuing a series of requests such that the first request specifies the base 131 | of the subtree and each subsequent request specifies the OID of the instance 132 | returned by the previous request. Such requests may be issued until the 133 | management application reaches some defined stopping condition (such as 134 | receiving a response for an instance beyond the end of the desired subtree) or 135 | the MIB contents are exhausted. The functionality offered by GetBulk is 136 | somewhat complex, but is logically equivalent to a series of GetNext requests. 137 | 138 | A more detailed discussion of how to handle each of these operations correctly 139 | follows in the sections below for each type of provider. 140 | 141 | ### Results 142 | 143 | Each operation has at least one result, which must be passed to the completion 144 | callback. In the case of GetBulk requests directed to a tabular data provider, 145 | there may be multiple results; in that case, the results should be passed to the 146 | completion callback as members of a dense array. Each such result must be one 147 | of the following: 148 | 149 | #### undefined 150 | 151 | If the completion callback is invoked with no argument or an argument of type 152 | `undefined`, the agent will interpret this to mean that the requested instance 153 | does not exist within the subtree. If the request was of type GetRequest or 154 | SetRequest (see `ProviderRequest.op` below), this will result in an error being 155 | returned to the management application in this varbind. If the request was of 156 | type GetNextRequest or GetBulkRequest, the agent will retry the request in the 157 | next MIB subtree for which a provider has been registered, or return an error 158 | indicating that the MIB view has been exhausted if none exists. This behaviour 159 | is intended to be compliant with [RFC 3416][]. 160 | 161 | #### An integer error code 162 | 163 | If the requested instance exists but a fatal error occurred during retrieval of 164 | the requested data value for that instance, an integer corresponding to the 165 | appropriate error code must be passed to the completion callback. Any of the 166 | error codes defined for the `error-status` object in section 3 of [RFC 3416][] 167 | may be passed; for convenience, the `snmp.pdu` object has symbolic constants 168 | attached to it associated with these error status values. 169 | 170 | Note that most operations are not permitted by the relevant standards to return 171 | all of these error codes. If a provider responds with an error code that is 172 | prohibited to the operation requested, the agent's behaviour is unspecified. 173 | The detailed guidance below for implementing each type of provider describes the 174 | circumstances in which each error code should be used. 175 | 176 | #### A varbind object (or an array thereof) 177 | 178 | Varbind objects are those created by calls to `snmp.varbind.createVarbind`. 179 | Most requests should result in the generation of one of these objects (in the 180 | case of GetRequest, SetRequest, and GetNextRequest) or an array containing 181 | several of these objects (in the case of GetBulkRequest). A varbind is simply a 182 | key/value pair in which the key is an OID and the value is a typed data value 183 | created by `snmp.data.createData`. In all cases, the `oid` member should be set 184 | to the OID of the instance whose value is being provided, which in some cases 185 | may be different from the OID that was requested. 186 | 187 | ### Scalar Providers 188 | 189 | To a scalar provider, Get, GetNext, and GetBulk requests can all be handled in 190 | exactly the same way. The agent will not direct any of these requests to a 191 | scalar provider that should not be satisfied by the singleton instance's data 192 | value. That is, a scalar provider should ignore whether the request is for the 193 | singleton instance or the object itself, whether the request is for the named 194 | instance or the next instance, and how many instances (in the case of GetBulk) 195 | are requested. 196 | 197 | To handle a Get, GetNext, or GetBulk request: 198 | 199 | 1. Retrieve or compute the data item requested. If the data item's value could 200 | not be computed, invoke the completion callback with the single argument 201 | `snmp.pdu.genErr`. 202 | 1. Use `snmp.data.createData` to construct a data object of the appropriate type 203 | with the value obtained during the previous step. 204 | 1. Use `snmp.varbind.createVarbind` to construct a varbind object from the data 205 | object created in the previous step. The OID of the varbind must be the OID of 206 | the singleton instance (i.e., it must end in `.0`). 207 | 1. Invoke the completion callback with the varbind object as its sole argument. 208 | 209 | To handle a Set request: 210 | 211 | 1. If the object is not modifiable, invoke the completion callback with the 212 | single argument `snmp.pdu.notWritable`. This addresses clauses (2) and (9) in 213 | section 4.2.5 of [RFC 3416][]. 214 | 1. Inspect the value provided according to the rules described in section 4.2.5 215 | of [RFC 3416][], specifically clauses (3) through (6) and (10). If the value 216 | fails any of the specified criteria, invoke the completion callback with a 217 | single argument of `snmp.pdu.wrongType`, `snmp.pdu.wrongLength`, 218 | `snmp.pdu.wrongEncoding`, `snmp.pdu.wrongValue`, or 219 | `snmp.pdu.inconsistentValue`, respectively. 220 | 1. Attempt to make the requested operation take effect. The meaning of this is 221 | specific to the semantics of the MIB subtree and may include persisting 222 | something in a database, altering a system parameter, or performing an operation 223 | on a remote server. If the operation fails because of resource exhaustion or 224 | unavailability (even if retryable), invoke the completion callback with a single 225 | argument of `snmp.pdu.resourceUnavailable`. If the operation fails for any 226 | other reason, invoke the completion callback with a single argument of 227 | `snmp.pdu.genErr`. 228 | 1. Use `snmp.data.createData` to construct a data object of the appropriate type 229 | with the value assigned during the previous step. 230 | 1. Use `snmp.varbind.createVarbind` to construct a varbind object from the data 231 | object created in the previous step. The OID of the varbind must be the OID of 232 | the singleton instance (i.e., it must end in `.0`). 233 | 1. Invoke the completion callback with the varbind object as its sole argument. 234 | 235 | ### Tabular Providers 236 | 237 | To handle a GetRequest: 238 | 239 | 1. Determine whether the instance requested refers to a row in the table that 240 | exists. If not, or if the object itself was requested, invoke the completion 241 | callback with no argument. 242 | 1. Retrieve or compute the data item requested. If the data item's value could 243 | not be computed, invoke the completion callback with the single argument 244 | `snmp.pdu.genErr`. 245 | 1. Use `snmp.data.createData` to construct a data object of the appropriate type 246 | with the value obtained during the previous step. 247 | 1. Use `snmp.varbind.createVarbind` to construct a varbind object from the data 248 | object created in the previous step. The OID of the varbind must be the OID of 249 | the instance requested. 250 | 1. Invoke the completion callback with the varbind object as its sole argument. 251 | 252 | To handle a SetRequest: 253 | 254 | SetRequests are handled in the same way as by scalar providers, with the 255 | following exceptions: 256 | 257 | 1. Prior to performing the procedures described above, determine whether the 258 | instance requested refers to a row in the table exists. If not, and it cannot 259 | be created, or if the object itself was requested, or if the provider does not 260 | support modifying its data, invoke the completion callback with the single 261 | argument `snmp.pdu.notWritable`. 262 | 1. In the second step, clauses (7) and (8) must be considered as well, and the 263 | completion callback invoked with a single argument of `snmp.pdu.noCreation` or 264 | `snmp.pdu.inconsistentName`, respectively. 265 | 266 | To handle a GetNextRequest: 267 | 268 | 1. Determine which instance, if any, immediately follows that specified in the 269 | request in the lexicographically ordered OID space, or would if such an instance 270 | existed. Examples of lexicographic OID ordering may be found in [RFC 3416][] 271 | sections 4.2.2.1 and 4.2.3.1. If there is no such instance in the column, 272 | invoke the completion callback with no argument. 273 | 1. Retrieve or compute the data item associated with the instance identified in 274 | the previous step. If the data item's value could not be computed, invoke the 275 | completion callback with the single argument `snmp.pdu.genErr`. 276 | 1. Use `snmp.data.createData` to construct a data object of the appropriate type 277 | with the value obtained during the previous step. 278 | 1. Use `snmp.varbind.createVarbind` to construct a varbind object from the data 279 | object created in the previous step. The OID of the varbind must be the OID of 280 | the instance identified in the first step above (i.e., not that which was 281 | referenced in the request). 282 | 1. Invoke the completion callback with the varbind object as its sole argument. 283 | 284 | To handle a GetBulkRequest: 285 | 286 | A GetBulkRequest is handled in the same manner as a series of GetNextRequests in 287 | which each request's OID after the first is the OID of the response to the 288 | previous request. The only exception is that the completion callback should not 289 | be invoked until all iterations have been performed and the result of each 290 | iteration stored in a dense array. The completion callback must be invoked with 291 | the single argument of this array, each element of which is the result of the 292 | corresponding iteration. If an iteration's result if `undefined`, all 293 | subsequent results must be `undefined` as well. If a single iteration is 294 | requested, the use of the array to store the result is optional; it may instead 295 | be passed directly to the completion callback. 296 | 297 | ### Important Notes 298 | 299 | 1. If a GetNext or GetBulk request is received by a tabular provider for a 300 | column object itself (i.e., no instance is identified), the response should 301 | refer to the first instance in the table according to lexicographical OID 302 | ordering. If the table is empty or no row in the table has a value in the 303 | requested column, the result of this portion of the operation is the empty 304 | value; i.e., `undefined`. If the table is not empty but the first row(s) have 305 | no value in the requested column, the value should be returned from the first 306 | row in the column which does have a value. 307 | 308 | 1. The agent does not validate the type of the data object passed by a provider 309 | back to the agent, nor passed into a provider as the value of a SetRequest, 310 | against any MIB definition. Values of incorrect or unexpected type passed to 311 | the agent by a provider will be returned to the management application 312 | unmodified. This behaviour may change in a future API revision. 313 | 314 | 1. The processing of Set requests is clearly intended by the standard to treat 315 | multiple varbinds within a single set request message as an atomic transaction. 316 | This implementation does not provide those semantics and does not conform to the 317 | transactionality requirements laid out in [RFC 3416][] section 4.2.5. While the 318 | use of SNMP for control operations is somewhere between uncommon and nonexistent 319 | in practice, be aware that if your deployment does expect to perform control 320 | operations, agents based on snmpjs must not be used if you expect or require the 321 | atomicity specified by [RFC 3416][] section 4.2.5. 322 | 323 | 1. For scalar providers whose data items are relatively inexpensive to retrieve, 324 | portions of the above procedures may be performed automatically by using the 325 | `snmp.provider.readOnlyScalar` or `snmp.provider.writableScalar` interfaces. 326 | Specifically, these interfaces will create the varbind objects from data value 327 | objects and, in the case of read-only values, will reject SetRequests 328 | appropriately. They also invoke the completion callback. 329 | 330 | 1. The `snmp.provider.readOnlyScalar` and `snmp.provider.writableScalar` utility 331 | routines must not be used by tabular providers. 332 | 333 | # ProviderRequest 334 | 335 | This section describes the request objects passed to providers; the control flow 336 | is described above. 337 | 338 | Instances of ProviderRequest provide no methods. All members and all contents 339 | of those members are read-only and must not be modified by consumers. Any 340 | attempt to modify a member will result in an exception. Any attempt to modify 341 | the contents of a member will result either in an exception or undefined 342 | behaviour. 343 | 344 | ## ProviderRequest.done 345 | 346 | This callback function must be invoked by exactly one handler for each MIB 347 | provider to which a request is issued. Its sole argument is the result of the 348 | requested operation, which is interpreted by the agent as described above. 349 | 350 | ## ProviderRequest.op 351 | 352 | The requested operation, which will be one of the four request types in 353 | `snmp.pdu.{Get,GetNext,GetBulk,Set}Request`. 354 | 355 | The complete set of rules defining each of these operations may be found in 356 | [RFC 3416][], but the discussion in this document should be sufficient to write 357 | correct MIB providers for many types of commonly-encountered data. Operations 358 | of other types will not be passed to a MIB provider. 359 | 360 | ## ProviderRequest.oid 361 | 362 | The OID found in the actual request from the management application, as a 363 | string. 364 | 365 | Example: 366 | 367 | '.1.3.6.1.2.1.2.2.1.3.14' 368 | 369 | ## ProviderRequest.addr 370 | 371 | The OID found in the actual request from the management application, as an array 372 | of non-negative integers less than 2^31. 373 | 374 | Example: 375 | 376 | [ 1, 3, 6, 1, 2, 1, 2, 2, 1, 3, 14 ] 377 | 378 | ## ProviderRequest.value 379 | 380 | The value to which the management application requested that this instance be 381 | set, in the case of a SetRequest. If the `op` member is other than 382 | `snmp.pdu.SetRequest`, this member will be absent or undefined and should be 383 | ignored by the provider. 384 | 385 | The member is an object instance descended from `snmp.data.SnmpData` and 386 | provides the interfaces defined in the [Protocol API][]. 387 | 388 | ## ProviderRequest.node 389 | 390 | A `MIBNode` object corresponding to the location in the MIB where the provider 391 | for this subtree was located. This object provides the interfaces described in 392 | the [MIB API][]. 393 | 394 | ## ProviderRequest.instance 395 | 396 | The instance portion of the object identifier to which this request pertains, as 397 | an array of non-negative integers less than 2^31. This is the portion after the 398 | column number in a table entry or `[ 0 ]` for scalars. If the request pertains 399 | to something that it not an instance, this member will be undefined or absent. 400 | 401 | Note that the correct interpretation of this value may require it to be 402 | translated into some other form; e.g., a string. The agent has no awareness of 403 | the expected type of the index into a table, even if it is defined by a MIB 404 | definition. This behaviour may change in a future API revision. 405 | 406 | Example: 407 | 408 | [ 14 ] 409 | 410 | ## ProviderRequest.iterate 411 | 412 | The number of consecutive instances following the specified instance for which 413 | data is requested. This will be set to 1, absent, or undefined unless the 414 | requested operation is GetBulkRequest, and may be ignored by providers for all 415 | other operations. 416 | 417 | If this member is set to 1 and the operation type is GetBulkRequest, the 418 | provider should behave exactly as it would if the operation type were 419 | GetNextRequest. 420 | 421 | # Convenience Interfaces 422 | 423 | While the interfaces described above are sufficient to write any MIB provider 424 | that may be needed, several convenience interfaces are also available to reduce 425 | the amount of boilerplate code required to do so. Use of these interfaces is 426 | optional but recommended where appropriate. 427 | 428 | These interfaces may be found as children of the `snmp.provider` object. 429 | 430 | ## readOnlyScalar(prq, rsd) 431 | 432 | This function constructs the appropriate response to the `ProviderRequest` `prq` 433 | from the data object `rsd`, then invokes the request's completion callback. It 434 | is appropriate only for scalar entities whose values cannot be changed by a 435 | SetRequest. 436 | 437 | Example: 438 | 439 | function 440 | myProvider(prq) 441 | { 442 | var val = snmp.data.createData({ type: 'OctetString', 443 | value: 'foo' }); 444 | snmp.provider.readOnlyScalar(prq, val); 445 | } 446 | 447 | In general it is assumed that data may take some time to acquire from its 448 | source, in which case this can be done asynchronously: 449 | 450 | function 451 | myProvider(prq) 452 | { 453 | slowOperation(function (result, err) { 454 | var val; 455 | 456 | if (err) { 457 | prq.done(snmp.pdu.genErr); 458 | } else { 459 | var val = snmp.data.createData({ 460 | type: 'OctetString', 461 | value: result 462 | }); 463 | snmp.provider.readOnlyScalar(prq, val); 464 | } 465 | }); 466 | } 467 | 468 | Note that if the computation of the result is especially expensive, it may be 469 | preferable to short-circuit the error case in which `prq.op` indicates a 470 | SetRequest. This convenience function handles this case correctly, but only 471 | after the response has been computed. 472 | 473 | ## writableScalar(prq[, rsd]) 474 | 475 | Analogous to `readOnlyScalar`, this function constructs an appropriate response 476 | to any request for a scalar data item that may be modified. In the case of a 477 | SetRequest, your provider must perform whatever actions are necessary to persist 478 | the modification or otherwise cause it to become effective, then invoke this 479 | function. Note that it is not necessary to pass any value argument if the 480 | operation was successful. In response to requests for other operations, it 481 | should obtain the value of the data item and pass it to this function as for 482 | `readOnlyScalar`. 483 | 484 | If a SetRequest operation fails because the data value could not be updated or 485 | is of inappropriate type or out of range, your provider should invoke the 486 | completion callback directly with the appropriate error code as described above. 487 | 488 | --- 489 | [RFC 3416]: http://www.ietf.org/rfc/rfc3416.txt 490 | [Protocol API]: protocol.html 491 | [MIB API]: mib.html 492 | -------------------------------------------------------------------------------- /docs/snmp.restdown: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction to SNMP and snmpjs 3 | markdown2extras: wiki-tables, code-friendly 4 | apisections: 5 | --- 6 | 7 | # Introduction to SNMP and snmpjs 8 | 9 | The Simple Network Management Protocol (SNMP) has two strikes against it right 10 | from the outset: its name includes the toxicity warning keyword 'simple', and 11 | its specification consists of literally dozens of RFCs (many now on their second 12 | or third revisions) that read like they were written by lawyers. Bit-twiddling 13 | lawyers. If you already know SNMP well, you can probably skip this document and 14 | go straight to the snmpjs API reference. If not, read on for a condensed -- 15 | some might even say oversimplified -- explanation of just what snmpjs allows you 16 | to do. 17 | 18 | # In a Nutshell 19 | 20 | SNMP defines a typed key/value data store backed by the actual state of a 21 | running computer, and a remote retrieval mechanism using datagrams (UDP, 22 | basically). That's it. Because the early (and still most commonly deployed) 23 | variants included no meaningful authentication or privacy mechanisms, the 24 | database is often made read-only, and because the contents of the database 25 | represent the state of a live system, the database contents are normally treated 26 | by clients as uncacheable. Without reading any specifications or the source 27 | code to an implementation, it's easy to understand why its authors considered 28 | the protocol to be simple: it is. 29 | 30 | # Basic Terminology 31 | 32 | There are a number of other terms used in the standards, but these are the 33 | essential ones: 34 | 35 | - The database itself is called the *Management Information Base (MIB)*. 36 | - The entries in the database that describe data are called *object types*; the 37 | data items themselves are called *instances* of these object types. 38 | - The piece of software that brokers requests for data (the "server") and 39 | generates telemetry when data values change is called an *agent*. 40 | - The piece of software that makes requests (the "client") is called a 41 | *management application* or *manager*. 42 | - Finally, network hosts with agents or managers installed are called 43 | *entities*. 44 | 45 | # Access Control 46 | 47 | In the original SNMP specification, access to objects and their data was to be 48 | controlled by the exchange of a simple string, essentially a plaintext password, 49 | called the community. The intent was that all management applications and 50 | agents in an administrative domain would be configured as part of a single 51 | community and would exchange and verify the same community name. This 52 | mechanism, while extremely weak, is still in widespread use, though other, 53 | stronger, mechanisms have since been proposed and implemented. 54 | 55 | There are three main versions of SNMP in common deployment: 1, 2c, and 3. 56 | SNMPv1 is largely obsolete, replaced by the mostly-compatible and very similar 57 | v2c, so-named because it refers to "community-based v2" and preserves the same 58 | extremely weak but simple authentication mechanism present in v1. The original 59 | proposal for SNMPv2 incorporated a complex and unpopular security mechanism, 60 | eventually dropped in favour of v2c but later implemented as a component of v3. 61 | Nearly all SNMP management applications and agents support v2c, and, to the 62 | extent that they differ, v1 as well. While there are several software packages 63 | available that support SNMPv3, it is not widely deployed. The snmpjs software 64 | supports v1 and v2c only; it can neither parse nor generate a v3 message, and 65 | lacks utility routines for manipulating v3-specific objects. 66 | 67 | # The Management Information Base (MIB) 68 | 69 | The term MIB is used interchangeably to mean either the entire database an SNMP 70 | entity makes available to management applications, or a thematically unified 71 | subset of that information. For example, standards documents typically use "the 72 | MIB" to mean the whole database, while administrators frequently speak of 73 | support for "a MIB" to mean a subset of that data relevant to a particular 74 | application or system component ("the Sun FMA MIB"), or a text file that 75 | describes its format ("SUN-FM-MIB.mib"). It is important to note that the 76 | contents of such a file are purely advisory; management applications rely on 77 | them for metadata about what objects are likely to be accessible, how they 78 | should be accessed, and how their data contents should be presented, but there 79 | is no guarantee that an agent or management application will adhere to the 80 | specified syntax when transmitting a data object. 81 | 82 | The MIB contains a hierarchically-named set of objects, each of which may have 83 | zero or more instances. An instance is an object with which a single data item 84 | is associated; each data item has a type and a value. An object may have no 85 | instances either because it is specified to serve only as a hierarchical 86 | container for other objects or because the SNMP entity does not contain anything 87 | of the type represented by the object. For example, a network host with no 88 | storage devices would have no instances of an object that represents a disk 89 | drive. The object namespace is an ordered sequence of integers in the range 90 | \[0, 255\], and such a sequence is called an object identifier or OID. OIDs are 91 | typically written as strings in dotted-decimal notation; e.g., `1.3.6.1`. Each 92 | component integer in the hierarchy typically has a convenient string name; in 93 | this example, the name is `iso.org.dod.internet`, which for historical reasons 94 | happens to be the root of most of the interesting parts of the OID space. 95 | 96 | Objects which may have instances come in two basic flavours: scalar and tabular. 97 | A scalar object has at most one instance, always named 0 (if it exists). For 98 | example, the nodename of a network host is available in in instance named 99 | mib-2.system.sysName.0. 100 | 101 | A tabular object consists of a subtree of objects whose leaves are instances 102 | corresponding to rows in the table. Such a subtree usually consists of an 103 | object referring to the table type, a single child object referring to a type 104 | specifying the format of each table entry, which in turn has one child object 105 | referring to a type specifying the format of each column in the table for which 106 | data items are available. For example, a table in which each row describes a 107 | software module and each module is described by an index, a name, and a version 108 | might be specified as: 109 | 110 | sunFmModuleTable 111 | | 112 | sunFmModuleEntry 113 | / | \ 114 | sunFmModuleIndex sunFmModuleName sunFmModuleVersion 115 | 116 | Each of these columnar objects would have one child for each instance that 117 | exists on the system. Each of the objects corresponding to the same row in the 118 | table has the same name beneath the column identifier. Therefore, if there are 119 | two rows named 1 and 2, the following instances would exist within the MIB, each 120 | with a data item associated with it: 121 | 122 | sunFmModuleTable.sunFmModuleEntry.sunFmModuleIndex.1 123 | sunFmModuleTable.sunFmModuleEntry.sunFmModuleIndex.2 124 | sunFmModuleTable.sunFmModuleEntry.sunFmModuleName.1 125 | sunFmModuleTable.sunFmModuleEntry.sunFmModuleName.2 126 | sunFmModuleTable.sunFmModuleEntry.sunFmModuleVersion.1 127 | sunFmModuleTable.sunFmModuleEntry.sunFmModuleVersion.2 128 | 129 | There are three general approaches to tabular instance naming (that is, 130 | indexing) in common use: 131 | 132 | - Integer indices, normally starting from 1 (i.e., the table is an 133 | array) 134 | 135 | - Complex identifiers such as an IP address or string (i.e., the table 136 | is an associative array, hash, or object) 137 | 138 | - A concatentation of multiple IP addresses, port numbers, strings, or 139 | other data that constructs a virtual multidimensional table (i.e., the 140 | table is a nested object or a content-addressable data store). 141 | 142 | Because each instance in the MIB references a single scalar data item, it is 143 | possible (and common) to use complex instance identifiers to form a 144 | content-addressable data store by using several of the columns within a 145 | conceptual table to form the name of an instance rather than (or in addition to) 146 | providing the values in those columns as data items within instances. The 147 | canonical example is the routing table, which is indexed by the aggregation of 148 | the destination prefix and mask just as it is inside the networking stack 149 | itself. 150 | 151 | Further examples, descriptions, and specifications of tabular and scalar data 152 | structures can be found in [RFC 1155][], [RFC 1212][], [RFC 2578][], 153 | [RFC 3416][], and the many documents specifying the format of various portions 154 | of the MIB. 155 | 156 | # Messages and PDUs 157 | 158 | An SNMP message is a datagram, almost always implemented over UDP. Each message 159 | contains a small amount of metadata and exactly one Protocol Data Units (PDU). 160 | A PDU contains an operation type, some metadata, and possibly some object 161 | data. There are four basic types of operation: requests, responses, traps, and 162 | reports. Reports have a standard role only in SNMPv3 and are not discussed 163 | here. See [RFC 3412][] for details on reports. 164 | 165 | Within any PDU type, an object, including its name (OID) and possibly its data 166 | value, is called a variable binding or varbind. For practical purposes, this is 167 | best thought of simply as a triple (name, type, value). 168 | 169 | ### Requests 170 | 171 | There are four types of requests that are generated by management applications 172 | and sent to agents on managed entities: Get, GetNext, GetBulk, and Set. As 173 | their names imply, three of these requests attempt to retrieve existing data and 174 | one attempts to modify it. Again, since the "model" in SNMP is a live system, 175 | modifying the contents of the database modifies the system and affects its 176 | operation. 177 | 178 | The Get and Set requests retrieve or modify a single data item, specified by an 179 | OID. The data item may be scalar or tabular data, but it must itself be scalar 180 | (that is, these requests cannot operate on multiple data items, even if they are 181 | logically contiguous or part of a tabular structure). In general, if the OID 182 | names a scalar object definition rather than its instance, the request is 183 | treated as if it referenced the instance. This may not apply to OIDs that name 184 | tabular object definitions such as entries or columns. 185 | 186 | The GetNext request is similar to the Get request in that it operates on a 187 | single data item. Unlike a Get request, however, the GetNext request operates 188 | on the object whose name *immediately follows* the object named in the request. 189 | This allows a management application to traverse all or part of the MIB without 190 | knowing the names of all the objects it contains. 191 | 192 | The GetBulk request works like a list of Get and/or GetNext requests submitted 193 | as a single request, and expects the results of those requests in a single 194 | response. This was intended to allow more rapid processing of tabular data; by 195 | requesting, for example, the next 10 data items following each of the objects 196 | naming columns in a table, a management client could make fewer requests to 197 | retrieve the same data, and an agent could respond with multiple pieces of data 198 | that are likely to be stored together anyway without having to look them up 199 | multiple times. Unfortunately, this is less useful than it sounds, because the 200 | amount of data that can be exchanged in a single transaction is limited by the 201 | size of datagrams that can be transported between the managed entity and the 202 | management host. That may be 1500 bytes, or even less in some cases, and is 203 | always limited to just under 64kB in IPv4. While the standard makes an 204 | allowance for this by requiring that an error message be returned if the 205 | response would be too large to transport, it is often impossible to know how 206 | large a message can be sent to the requesting host. 207 | 208 | ### Responses 209 | 210 | Responses are generated by agents when they receive requests. A response PDU 211 | has a 1-1 mapping to the request PDU to which it pertains. It contains metadata 212 | such as error state and may include one or more varbinds describing the 213 | result(s) of the requested operation(s). 214 | 215 | ### Traps 216 | 217 | There are three types of trap: Trap, SNMPv2_Trap, and InformRequest. All three 218 | may be generated by any entity and serve the purpose of providing notification 219 | of an asynchronous event. The original SNMPv1 Trap type is obsolete; the SNMPv2 220 | Trap performs the same function but uses a different on-wire format. An 221 | InformRequest is very similar to an SNMPv2 Trap, but will be periodically 222 | retransmitted until the transmitter receives an acknowledgement Response from 223 | the recipient (both of the older Trap types are fire-and-forget). 224 | 225 | Any of the traps may include varbinds that contain object data items relevant to 226 | the event that occurred. 227 | 228 | # Agents 229 | 230 | An agent is responsible for receiving requests, delegating them to appropriate 231 | software, and assembling and transmitting responses. Agents normally listen on 232 | UDP port 161. There are a wide variety of mechanisms by which an agent may 233 | delegate requests; the most common are: 234 | 235 | - Direct incorporation, in which the agent contains one or more software 236 | components that satisfy requests from data contained within the agent 237 | itself. This is commonly used for portions of the MIB that are 238 | introspective in nature; that is, which concern the functioning of the 239 | agent itself. Some agent software also directly incorporates commonly 240 | used portions of the MIB defined by standards. 241 | 242 | - Loadable modules, in which the agent loads at runtime a set of 243 | software components specified by the administrator. Each module 244 | handles a subset of the MIB and executes in the same address space or 245 | virtual execution environment as the rest of the agent. 246 | 247 | - AgentX, a mechanism defined by [RFC 2741][] for delegating portions of 248 | the MIB to subagents that do not directly handle SNMP communication 249 | but instead communicate with the master agent using a separate 250 | protocol. These subagents may be part of separate processes or 251 | execution environments, or may be contained within the same process or 252 | execution environment that contains the master agent. 253 | 254 | - Proprietary RPC mechanisms that delegate portions of the MIB to other 255 | processes, execution environments, or even separate virtual or 256 | physical hosts. 257 | 258 | The snmpjs software supports a loadable module mechanism using standard Node.js 259 | functionality. This mechanism could be used to implement either of the dynamic 260 | mechanisms, but at present there is no support for communicating with subagents 261 | that are not part of the Node.js process. 262 | 263 | # Trap Generators 264 | 265 | Any SNMP entity may generate traps, but in the common use case, managed entities 266 | containing agents will also generate traps. Because, prior to the introduction 267 | of Inform functionality in SNMPv3, traps were not acknowledged and datagram 268 | delivery is unreliable, trap generation by itself is usually considered 269 | insufficient. It must be supplemented by an agent providing access to relevant 270 | portions of the MIB so that a management application can poll the entity for its 271 | status in case a trap was not generated or delivered. In addition, it is likely 272 | that the data items included in a trap's varbinds will be a subset of those 273 | available in the managed entity's MIB. 274 | 275 | The agent itself, in most software architectures, will not actively poll the 276 | live system unless requests are being received, and is unlikely to be aware of 277 | conditions that require the generation of a trap. Therefore, trap generation is 278 | typically incorporated into other software components in the managed entity such 279 | as daemons that provide critical services or monitor the health of the system. 280 | The snmpjs package provides trap generator functionality that can be 281 | incorporated into any Node.js application. 282 | 283 | # Management Applications and Trap Listeners 284 | 285 | These components of SNMP infrastructure generate requests for data items and 286 | direct them to agents on managed entities throughout the network, then receive 287 | and present that information to network administrators. They also typically 288 | receive traps from the same or an overlapping set of managed entities and 289 | present information about them as well. Implementations vary widely, from 290 | simple single-purpose scripts that log events, to packages of discrete utilities 291 | that can perform many functions, to large-scale shrinkwrapped software that 292 | incorporates SNMP as part of a comprehensive management system with multiple 293 | presentation choices. 294 | 295 | The snmpjs package includes some simple example utilities that illustrate how to 296 | use the library to build management applications. It does not include a 297 | full-featured SNMP management application, nor does it include a comprehensive 298 | suite of utilities appropriate for managing a network. 299 | 300 | --- 301 | [RFC 1155]: http://www.ietf.org/rfc/rfc1155.txt 302 | [RFC 1212]: http://www.ietf.org/rfc/rfc1212.txt 303 | [RFC 2578]: http://www.ietf.org/rfc/rfc2578.txt 304 | [RFC 2741]: http://www.ietf.org/rfc/rfc2741.txt 305 | [RFC 3412]: http://www.ietf.org/rfc/rfc3412.txt 306 | [RFC 3416]: http://www.ietf.org/rfc/rfc3416.txt 307 | -------------------------------------------------------------------------------- /lib/agent.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var assert = require('assert'); 6 | var dgram = require('dgram'); 7 | var util = require('util'); 8 | var Listener = require('./listener'); 9 | var message = require('./protocol/message'); 10 | var PDU = require('./protocol/pdu'); 11 | var varbind = require('./protocol/varbind'); 12 | var data = require('./protocol/data'); 13 | var MIB = require('./mib'); 14 | var ProviderRequest = require('./provider').ProviderRequest; 15 | 16 | var AGENT_PROBES = { 17 | /* id, op, srcaddr */ 18 | 'agent-req-start': [ 'int', 'int', 'char *' ], 19 | /* id, op, srcaddr, status, index */ 20 | 'agent-req-done': [ 'int', 'int', 'char *', 'int', 'int' ], 21 | /* id, op, index, oid */ 22 | 'agent-varbind-dispatch': [ 'int', 'int', 'int', 'char *' ], 23 | /* id, op, index, oid, result */ 24 | 'agent-varbind-result': [ 'int', 'int', 'int', 'char *', 'char *' ] 25 | }; 26 | 27 | function 28 | AgentMIBDecor(prov, column) 29 | { 30 | this._scalar = (prov.columns) ? false : true; 31 | this._column = (column === true) ? true : false; 32 | 33 | if (util.isArray(prov.handler)) 34 | this._handler = prov.handler; 35 | else 36 | this._handler = [ prov.handler ]; 37 | } 38 | 39 | AgentMIBDecor.prototype.instancePossible = function () { 40 | return (this._scalar || this._column); 41 | }; 42 | 43 | Agent.prototype._add_provider = function _add_provider(prov) { 44 | var self = this; 45 | var decor; 46 | var node; 47 | 48 | if (typeof (prov) !== 'object') 49 | throw new TypeError('prov (object) is required'); 50 | if (typeof (prov.oid) !== 'string') 51 | throw new TypeError('prov.oid (string) is required'); 52 | if (typeof (prov.handler) !== 'function' && 53 | (typeof (prov.handler) !== 'object' || !util.isArray(prov.handler))) 54 | throw new TypeError('prov.handler (function) is required'); 55 | if (typeof (prov.columns) !== 'undefined' && 56 | (typeof (prov.columns) !== 'object' || !util.isArray(prov.columns))) 57 | throw new TypeError('prov.columns must be an array'); 58 | 59 | node = this._mib.add(prov); 60 | decor = new AgentMIBDecor(prov); 61 | node.decorate({ tag: '_agent', obj: decor }); 62 | 63 | if (prov.columns) { 64 | prov.columns.forEach(function (c) { 65 | node = self._mib.add(prov.oid + '.' + c); 66 | decor = new AgentMIBDecor(prov, true); 67 | node.decorate({ tag: '_agent', obj: decor }); 68 | }); 69 | } 70 | }; 71 | 72 | Agent.prototype.addProviders = function addProviders(prov) { 73 | var self = this; 74 | 75 | if (typeof (prov) !== 'object') 76 | throw new TypeError('prov (object) is required'); 77 | 78 | if (util.isArray(prov)) { 79 | prov.forEach(function (p) { 80 | self._add_provider(p); 81 | }); 82 | } else { 83 | this._add_provider(prov); 84 | } 85 | }; 86 | 87 | function 88 | Agent(options) 89 | { 90 | var self = this; 91 | 92 | Listener.call(this, options); 93 | 94 | if (typeof (options.dtrace) !== 'object') 95 | throw new TypeError('options.dtrace (object) is required'); 96 | 97 | this._dtrace = options.dtrace; 98 | 99 | Object.keys(AGENT_PROBES).forEach(function (p) { 100 | var args = AGENT_PROBES[p].splice(0); 101 | args.unshift(p); 102 | 103 | self._dtrace.addProbe.apply(self._dtrace, args); 104 | }); 105 | 106 | this._dtrace.enable(); 107 | 108 | this._mib = new MIB(); 109 | } 110 | util.inherits(Agent, Listener); 111 | 112 | /* 113 | * The provider is expected to provide one of three things for each iteration 114 | * requested of it: 115 | * 116 | * - undefined, meaning that there is no matching instance. This should happen 117 | * only for tabular providers; scalar providers are never passed GetNext 118 | * requests nor any Get or Set with an instance other than 0. 119 | * 120 | * - an integer, representing an error status from the set of errors enumerated 121 | * in protocol/pdu.js. 122 | * 123 | * - an instance of SnmpVarbind containing the data requested. 124 | * 125 | * These end up here, one way or another, and we just stuff them into the 126 | * response object. 127 | */ 128 | Agent.prototype._varbind_set_single = 129 | function _varbind_set_single(req, rsp, idx, vb) { 130 | if (typeof (vb) === 'undefined' && req.pdu.op == PDU.GetNextRequest) { 131 | rsp.pdu.varbinds[idx] = req.pdu.varbinds[idx].clone(); 132 | rsp.pdu.varbinds[idx].data = data.createData({ type: 'Null', 133 | value: data.noSuchInstance }); 134 | } else if (typeof (vb) === 'number') { 135 | if (req.pdu.op != PDU.SetRequest && vb != PDU.genErr) { 136 | this.log.warn({ snmpmsg: req }, 137 | 'provider attempted to set prohibited ' + 138 | 'error code ' + vb + ' for varbind ' + idx); 139 | vb = PDU.genErr; 140 | } 141 | rsp.pdu.varbinds[idx] = req.pdu.varbinds[idx].clone(); 142 | if (!rsp.pdu.error_status || idx + 1 < rsp.pdu.error_index) { 143 | rsp.pdu.error_status = vb; 144 | rsp.pdu.error_index = idx + 1; 145 | } 146 | } else if (typeof (vb) !== 'object' || !varbind.isSnmpVarbind(vb)) { 147 | throw new TypeError('Response data is of incompatible type'); 148 | } else { 149 | rsp.pdu.varbinds[idx] = vb; 150 | } 151 | }; 152 | 153 | Agent.prototype._transmit_response = function _transmit_response(sock, rsp) { 154 | var dst = rsp.dst; 155 | var local_sock = false; 156 | 157 | rsp.encode(); 158 | this._log.trace({ raw: rsp.raw, dst: dst, snmpmsg: rsp }, 159 | 'Sending SNMP response message'); 160 | 161 | if (!sock) { 162 | sock = dgram.createSocket(dst.family); 163 | local_sock = false; 164 | } 165 | sock.send(rsp.raw.buf, 0, rsp.raw.len, dst.port, dst.address, 166 | function (err, len) { 167 | if (local_sock) 168 | sock.close(); 169 | }); 170 | }; 171 | 172 | Agent.prototype._do_getset = function _do_getset(req, rsp) { 173 | var self = this; 174 | var nvb = req.pdu.varbinds.length; 175 | var ndone = 0; 176 | 177 | req.pdu.varbinds.forEach(function (vb, i) { 178 | var node = self._mib.lookup(vb.oid); 179 | var prq = new ProviderRequest(req.pdu.op, vb.oid, node); 180 | var decor = node.decor('_agent'); 181 | var handler; 182 | 183 | if (!node || !decor || !decor.instancePossible()) { 184 | handler = [ function _getset_nsohandler(pr) { 185 | var rsd = data.createData({ type: 'Null', 186 | value: data.noSuchObject }); 187 | var rsvb = varbind.createVarbind({ 188 | oid: pr.oid, data: rsd }); 189 | pr.done(rsvb); 190 | } ]; 191 | } else { 192 | if (!prq.instance || decor._scalar && 193 | (prq.instance.length > 1 || prq.instance[0] != 0)) { 194 | handler = [ function _getset_nsihandler(pr) { 195 | var rsd = data.createData({ 196 | type: 'Null', 197 | value: data.noSuchInstance 198 | }); 199 | var rsvb = varbind.createVarbind({ 200 | oid: pr.oid, data: rsd }); 201 | pr.done(rsvb); 202 | } ]; 203 | } else { 204 | if (prq.op == PDU.SetRequest) 205 | prq._value = vb.data; 206 | handler = decor._handler; 207 | } 208 | } 209 | 210 | prq._done = function _getset_done(rsvb) { 211 | self._varbind_set_single(req, rsp, i, rsvb); 212 | if (++ndone == nvb) 213 | self._transmit_response(req._conn_recv, rsp); 214 | }; 215 | handler.forEach(function (h) { 216 | h(prq); 217 | }); 218 | }); 219 | }; 220 | 221 | Agent.prototype._getnext_lookup = function _getnext_lookup(oid, exactok) { 222 | var node; 223 | var addr = data.canonicalizeOID(oid); 224 | var match; 225 | var decor; 226 | 227 | oid = addr.join('.'); 228 | node = this._mib.lookup(oid); 229 | 230 | if (!node) 231 | return (null); 232 | 233 | decor = node.decor('_agent'); 234 | match = function (n) { 235 | var d = n.decor('_agent'); 236 | 237 | return (d && d.instancePossible() || false); 238 | }; 239 | 240 | /* 241 | * Please note that this code is optimised for readability because the 242 | * logic for choosing where to look for the next instance is somewhat 243 | * complex. It should be very apparent from this that we are in fact 244 | * covering all possible cases, and doing the right thing for each. 245 | */ 246 | 247 | /* 248 | * Exact match, the node is a column. Use the node if an exact match is 249 | * ok; the provider will figure out the first row, or else we'll end up 250 | * right back here with exactok clear. 251 | */ 252 | if (node.oid === oid && decor && decor._column && exactok) 253 | return (node); 254 | 255 | /* 256 | * Exact match, the node is a column but we need the next subtree. 257 | * Walk the parent starting from the next sibling. 258 | */ 259 | if (node.oid === oid && decor && decor._column) { 260 | return (this._mib.next_match({ 261 | node: node.parent, 262 | match: match, 263 | start: node.addr[node.addr.length - 1] + 1 264 | })); 265 | } 266 | 267 | /* 268 | * Exact match, the node is a scalar. Use it; we want the instance, 269 | * becuase it follows this OID in the lexicographical order. 270 | */ 271 | if (node.oid === oid && decor && decor._scalar) 272 | return (node); 273 | 274 | /* 275 | * Exact match, the node is just an OID. Walk the node from the 276 | * beginning to find any instances within this subtree. If there aren't 277 | * any, the walk will proceed back up and on to the next sibling. 278 | */ 279 | if (node.oid === oid && (!decor || !decor.instancePossible())) { 280 | return (this._mib.next_match({ 281 | node: node, 282 | match: match 283 | })); 284 | } 285 | 286 | /* 287 | * Ancestor, and the node is just an OID. Walk the node from the first 288 | * child after the first non-matching component. 289 | * 290 | * Ex: GetNext(.1.3.3.6.2) finds (.1.3); walk (.1.3), 4 291 | */ 292 | if (!decor || !decor.instancePossible()) { 293 | return (this._mib.next_match({ 294 | node: node, 295 | match: match, 296 | start: addr[node.addr.length] + 1 297 | })); 298 | } 299 | 300 | /* 301 | * Ancestor, and the node is a scalar. Walk the parent starting from 302 | * the next sibling, because there can be only one intance below this 303 | * node. So no matter what the instance portion of the OID is, the next 304 | * instance in the MIB can't be in this subtree. 305 | */ 306 | if (decor._scalar) { 307 | return (this._mib.next_match({ 308 | node: node.parent, 309 | match: match, 310 | start: node.addr[node.addr.length - 1] + 1 311 | })); 312 | } 313 | 314 | /* 315 | * Ancestor, and the node is a column. Use the node if an exact match 316 | * is ok, otherwise keep going. Note that this is the same as the very 317 | * first case above; we don't actually care whether we're being asked 318 | * for the first row in the column or the next one after some identified 319 | * instance, because that's the provider's job to figure out. 320 | */ 321 | if (exactok) 322 | return (node); 323 | 324 | /* 325 | * Ancestor, and the node is a column. An exact match is not ok, so 326 | * walk the parent starting from the next sibling. Again, this is the 327 | * same as the case in which we hit the exact match -- we know we've 328 | * already asked this provider and we need to advance to another one. 329 | */ 330 | return (this._mib.next_match({ 331 | node: node.parent, 332 | match: match, 333 | start: node.addr[node.addr.length - 1] + 1 334 | })); 335 | }; 336 | 337 | Agent.prototype._do_getnext_one = 338 | function (req, rsp, i, oid, cookie, first) { 339 | var self = this; 340 | var node = this._getnext_lookup(oid, first); 341 | var nvb = req.pdu.varbinds.length; 342 | var prq = new ProviderRequest(req.pdu.op, oid, node); 343 | var handler; 344 | 345 | if (!node || !node.decor('_agent').instancePossible()) { 346 | handler = [ function _getset_nsohandler(pr) { 347 | var rsd = data.createData({ type: 'Null', 348 | value: data.endOfMibView }); 349 | var rsvb = varbind.createVarbind({ 350 | oid: pr.oid, data: rsd }); 351 | pr.done(rsvb); 352 | } ]; 353 | } else { 354 | handler = node.decor('_agent')._handler; 355 | } 356 | 357 | prq._done = function _getnext_done(rsvb) { 358 | if (rsvb !== undefined) { 359 | self._varbind_set_single(req, rsp, i, rsvb); 360 | if (++cookie.ndone == nvb) 361 | self._transmit_response(req._conn_recv, rsp); 362 | return; 363 | } 364 | self._do_getnext_one(req, rsp, i, prq.node.oid, cookie, false); 365 | }; 366 | 367 | handler.forEach(function (h) { 368 | h(prq); 369 | }); 370 | }; 371 | 372 | Agent.prototype._do_getnext = function _do_getnext(req, rsp) { 373 | var self = this; 374 | var cookie = { ndone: 0 }; 375 | 376 | req.pdu.varbinds.forEach(function (vb, i) { 377 | self._do_getnext_one(req, rsp, i, vb.oid, cookie, true); 378 | }); 379 | }; 380 | 381 | Agent.prototype._do_getbulk = function _do_getbulk(req, rsp) { 382 | /* XXX yuck */ 383 | }; 384 | 385 | Agent.prototype._process_req = function _process_req(req) { 386 | var rsp; 387 | 388 | assert.ok(req.version >= 0 && req.version <= 1); 389 | 390 | /* XXX check community here */ 391 | 392 | rsp = message.createMessage({ version: req.version, 393 | community: req.community }); 394 | rsp.dst = req.src; 395 | 396 | rsp.pdu = PDU.createPDU({ op: PDU.Response, 397 | request_id: req.pdu.request_id }); 398 | rsp.pdu.error_status = 0; 399 | rsp.pdu.error_index = 0; 400 | 401 | switch (req.pdu.op) { 402 | case PDU.GetRequest: 403 | case PDU.SetRequest: 404 | this._do_getset(req, rsp); 405 | break; 406 | case PDU.GetNextRequest: 407 | this._do_getnext(req, rsp); 408 | break; 409 | case PDU.GetBulkRequest: 410 | this._do_getbulk(req, rsp); 411 | break; 412 | case PDU.Response: 413 | case PDU.Trap: 414 | case PDU.InformRequest: 415 | case PDU.SNMPv2_Trap: 416 | case PDU.Report: 417 | default: 418 | Listener.prototype._process_msg.call(this, req); 419 | break; 420 | } 421 | }; 422 | 423 | Agent.prototype._process_msg = function _process_msg(msg) { 424 | this._process_req(msg); 425 | }; 426 | 427 | Agent.prototype._augment_msg = function _augment_msg(msg, conn) { 428 | msg._conn_recv = conn; 429 | }; 430 | 431 | Agent.prototype.request = function request(oid, handler, columns) { 432 | var prov; 433 | 434 | if (typeof (oid) === 'string') { 435 | if (typeof (handler) !== 'function') 436 | throw new TypeError('handler must be a function'); 437 | 438 | this.addProviders({ 439 | oid: oid, 440 | handler: handler, 441 | columns: columns 442 | }); 443 | 444 | return; 445 | } 446 | 447 | prov = oid; 448 | if (typeof (prov) === 'object') { 449 | this.addProviders(prov); 450 | return; 451 | } 452 | 453 | throw new TypeError('MIB provider must be specified'); 454 | }; 455 | 456 | module.exports = Agent; 457 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 3 | */ 4 | 5 | var dgram = require('dgram'); 6 | var util = require('util'); 7 | var dns = require('dns'); 8 | var os = require('os'); 9 | var Receiver = require('./receiver'); 10 | var message = require('./protocol/message'); 11 | var PDU = require('./protocol/pdu'); 12 | var varbind = require('./protocol/varbind'); 13 | var data = require('./protocol/data'); 14 | 15 | var request_id = 1; 16 | 17 | function 18 | Client(options) 19 | { 20 | Receiver.call(this, options); 21 | this._callbacks = {}; 22 | this._socket = this.createSocket(options.family || 'udp4'); 23 | this._startuptime = 0; 24 | this.__defineSetter__('startuptime', function (t) { 25 | if (typeof (t) === 'number') 26 | this._startuptime = t; 27 | else 28 | throw new TypeError('startuptime must be an integer'); 29 | }); 30 | this.__defineGetter__('hostip', function () { return _hostip; }); 31 | } 32 | util.inherits(Client, Receiver); 33 | 34 | function getUptime(client) 35 | { 36 | return (Math.floor((Date.now() / 10) - (client._startuptime * 100))); 37 | } 38 | 39 | function send(client, ip, port, req, cb) { 40 | if (typeof (cb) !== 'undefined' && typeof (cb) !== 'function') 41 | throw new TypeError('cb must be a function'); 42 | 43 | if (cb && 'request_id' in req.pdu) 44 | client._callbacks[req.pdu.request_id] = cb; 45 | 46 | req.encode(); 47 | client._socket.send(req._raw.buf, 0, req._raw.len, port, ip, 48 | function (err, bytes) 49 | { 50 | if (err) 51 | console.log(err); // FIXME: meh 52 | }); 53 | } 54 | 55 | function OIDNull(oid) 56 | { 57 | return varbind.createVarbind({ 58 | oid: oid, 59 | data: data.createData({ type: 'Null', value: 5 }) 60 | }); 61 | } 62 | 63 | Client.prototype._process_msg = function _process_msg(msg) { 64 | switch (msg.pdu.op) { 65 | case PDU.Response: 66 | this._callbacks[msg.pdu.request_id](msg); 67 | delete this._callbacks[msg.pdu.request_id]; 68 | break; 69 | case PDU.Trap: 70 | case PDU.InformRequest: 71 | case PDU.SNMPv2_Trap: 72 | case PDU.GetRequest: 73 | case PDU.SetRequest: 74 | case PDU.GetNextRequest: 75 | case PDU.GetBulkRequest: 76 | case PDU.Report: 77 | default: 78 | Receiver.prototype._process_msg.call(this, msg); 79 | break; 80 | } 81 | }; 82 | 83 | Client.prototype.get = function (ip, community, version, oid, cb) { 84 | send(this, ip, 161, message.createMessage({ 85 | version: version, 86 | community: community, 87 | pdu: PDU.createPDU({ 88 | op: PDU.GetRequest, 89 | request_id: request_id++, 90 | varbinds: [ OIDNull(oid) ] 91 | }) 92 | }), cb); 93 | }; 94 | 95 | Client.prototype.getNext = function (ip, community, version, oid, cb) { 96 | send(this, ip, 161, message.createMessage({ 97 | version: version, 98 | community: community, 99 | pdu: PDU.createPDU({ 100 | op: PDU.GetNextRequest, 101 | request_id: request_id++, 102 | varbinds: [ OIDNull(oid) ] 103 | }) 104 | }), cb); 105 | }; 106 | 107 | Client.prototype.set = function (ip, community, version, oid, value, cb) { 108 | send(this, ip, 161, message.createMessage({ 109 | version: version, 110 | community: community, 111 | pdu: PDU.createPDU({ 112 | op: PDU.SetRequest, 113 | request_id: request_id++, 114 | varbinds: [ 115 | varbind.createVarbind({ 116 | oid: oid, 117 | data: value 118 | }) 119 | ] 120 | }) 121 | }), cb); 122 | }; 123 | 124 | var _hostip = '127.0.0.1'; 125 | dns.lookup(os.hostname(), function (err, address, family) { 126 | _hostip = address; 127 | }); 128 | 129 | Client.prototype.trap = function (ip, community, options, varbinds) { 130 | if ('specific_trap' in options) { 131 | if (!('generic_trap' in options)) 132 | options.generic_trap = PDU.enterpriseSpecific; 133 | if (PDU.enterpriseSpecific == options.generic_trap && 134 | !('enterprise' in options)) 135 | throw new TypeError('options.enterprise is required '+ 136 | 'for enterpriseSpecific traps'); 137 | } 138 | if (!('generic_trap' in options)) 139 | throw new TypeError('Need either generic_trap or '+ 140 | 'specific_trap'); 141 | 142 | var self = this; 143 | send(this, ip, 162, message.createMessage({ 144 | version: 0, 145 | community: community, 146 | pdu: PDU.createPDU({ 147 | op: PDU.Trap, 148 | enterprise: options.enterprise || '1.3.6.1.4.1.3.1.1', 149 | generic_trap: options.generic_trap, 150 | specific_trap: options.specific_trap || 0, 151 | agent_addr: options.agent_addr || _hostip, 152 | time_stamp: options.time_stamp || options.uptime || 153 | getUptime(self), 154 | varbinds: varbinds 155 | }) 156 | })); 157 | }; 158 | 159 | Client.prototype.getBulk = function (ip, community, non_repeaters, repeaters, 160 | max_repetitions, cb) { 161 | var msg = message.createMessage({ 162 | version: 1, 163 | community: community, 164 | pdu: PDU.createPDU({ 165 | op: PDU.GetBulkRequest, 166 | request_id: request_id++, 167 | varbinds: non_repeaters.map(OIDNull). 168 | concat(repeaters.map(OIDNull)) 169 | }) 170 | }); 171 | msg.pdu.non_repeaters = non_repeaters.length; 172 | msg.pdu.max_repetitions = max_repetitions; 173 | send(this, ip, 161, msg, cb); 174 | }; 175 | 176 | Client.prototype.SNMPv2_Trap = 177 | Client.prototype.inform = function (ip, community, uptime, oid, varbinds, cb) { 178 | var self = this; 179 | send(this, ip, 162, message.createMessage({ 180 | version: 1, 181 | community: community, 182 | pdu: PDU.createPDU({ 183 | op: typeof (cb) === 'function' ? PDU.InformRequest : 184 | PDU.SNMPv2_Trap, 185 | request_id: request_id++, 186 | varbinds: [ 187 | varbind.createVarbind({ 188 | // sysUpTime.0 189 | oid: '1.3.6.1.2.1.1.3.0', 190 | data: data.createData({ 191 | type: 'TimeTicks', 192 | value: typeof (uptime) !== 193 | 'undefined' ? uptime : 194 | getUptime(self) 195 | }) 196 | }), 197 | varbind.createVarbind({ 198 | // snmpTrapOID.0 199 | oid: '1.3.6.1.6.3.1.1.4.1.0', 200 | data: data.createData({ 201 | type: 'ObjectIdentifier', 202 | value: oid 203 | }) 204 | }) 205 | ].concat(varbinds) 206 | }) 207 | }), cb); 208 | }; 209 | 210 | Client.prototype.close = function () { 211 | this._socket.unref(); 212 | }; 213 | 214 | module.exports = Client; 215 | -------------------------------------------------------------------------------- /lib/errors/message.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var util = require('util'); 6 | 7 | function 8 | EmptyMessageError() 9 | { 10 | this.name = 'EmptyMessageError'; 11 | } 12 | util.inherits(EmptyMessageError, Error); 13 | 14 | function 15 | MessageParseError(str, info) 16 | { 17 | this.name = 'MessageParseError'; 18 | this.message = str; 19 | this.token = info.token; 20 | this.offset = info.loc; 21 | this.expected = info.expected; 22 | } 23 | util.inherits(MessageParseError, Error); 24 | 25 | function 26 | NoSupportError(str) 27 | { 28 | this.name = 'NoSupportError'; 29 | this.message = str; 30 | } 31 | util.inherits(NoSupportError, Error); 32 | 33 | module.exports = { 34 | EmptyMessageError: EmptyMessageError, 35 | MessageParseError: MessageParseError, 36 | NoSupportError: NoSupportError 37 | }; 38 | -------------------------------------------------------------------------------- /lib/errors/varbind.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | var util = require('util'); 5 | 6 | function 7 | TypeConflictError(str) 8 | { 9 | this.name = 'TypeConflictError'; 10 | this.message = str; 11 | } 12 | util.inherits(TypeConflictError, Error); 13 | 14 | module.exports = { 15 | TypeConflictError: TypeConflictError 16 | }; 17 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | /* 6 | * SNMP is defined by a large set of specifications. See 7 | * http://www.snmp.com/protocol/snmp_rfcs.shtml as a starting point. 8 | */ 9 | 10 | var dtrace = require('dtrace-provider'); 11 | var Agent = require('./agent'); 12 | var Client = require('./client'); 13 | var TrapListener = require('./trap_listener'); 14 | var Logger = require('bunyan'); 15 | var MIB = require('./mib'); 16 | var provider = require('./provider'); 17 | var message = require('./protocol/message'); 18 | var PDU = require('./protocol/pdu'); 19 | var varbind = require('./protocol/varbind'); 20 | var data = require('./protocol/data'); 21 | var uint64_t = require('./protocol/uint64_t'); 22 | 23 | var agent_provider; 24 | 25 | function 26 | bunyan_serialize_raw(raw) 27 | { 28 | var obj = { 29 | buf: (raw.buf ? raw.buf.inspect() : ''), 30 | len: raw.len || 0 31 | }; 32 | return (obj); 33 | } 34 | 35 | function 36 | bunyan_serialize_endpoint(endpoint) 37 | { 38 | var obj = { 39 | family: endpoint.family || '', 40 | address: endpoint.address || '', 41 | port: endpoint.port || 0 42 | }; 43 | return (obj); 44 | } 45 | 46 | function 47 | defaultDTrace(name) 48 | { 49 | if (!agent_provider) 50 | agent_provider = dtrace.createDTraceProvider(name); 51 | 52 | return (agent_provider); 53 | } 54 | 55 | function 56 | defaultLogger(log, name) 57 | { 58 | var serializers = { 59 | err: Logger.stdSerializers.err, 60 | raw: bunyan_serialize_raw, 61 | origin: bunyan_serialize_endpoint, 62 | dst: bunyan_serialize_endpoint, 63 | snmpmsg: message.serializer 64 | }; 65 | 66 | if (log) { 67 | return (log.child({ 68 | component: 'snmp-agent', 69 | serializers: serializers 70 | })); 71 | } 72 | 73 | return (new Logger({ 74 | name: name, 75 | level: 'info', 76 | stream: process.stderr, 77 | serializers: serializers 78 | })); 79 | } 80 | 81 | module.exports = { 82 | createAgent: function createAgent(options) { 83 | if (!options) 84 | options = {}; 85 | if (!options.name) 86 | options.name = 'snmpjs'; 87 | if (!options.dtrace) 88 | options.dtrace = defaultDTrace(options.name); 89 | 90 | options.log = defaultLogger(options.log, options.name); 91 | 92 | return (new Agent(options)); 93 | }, 94 | 95 | createMIB: function createMIB(def) { 96 | var mib = new MIB(); 97 | if (def) 98 | mib.add(def); 99 | 100 | return (mib); 101 | }, 102 | 103 | createClient: function createClient(options) { 104 | if (!options) 105 | options = {}; 106 | if (!options.name) 107 | options.name = 'snmpjs'; 108 | 109 | options.log = defaultLogger(options.log, options.name); 110 | 111 | return (new Client(options)); 112 | }, 113 | 114 | createTrapListener: function createTrapListener(options) { 115 | if (!options) 116 | options = {}; 117 | if (!options.name) 118 | options.name = 'snmpjs'; 119 | 120 | options.log = defaultLogger(options.log, options.name); 121 | 122 | return (new TrapListener(options)); 123 | }, 124 | 125 | message: message, 126 | pdu: PDU, 127 | varbind: varbind, 128 | data: data, 129 | provider: provider, 130 | uint64_t: uint64_t 131 | }; 132 | -------------------------------------------------------------------------------- /lib/lexer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var ASN1 = require('asn1').Ber; 6 | 7 | function 8 | Lexer() 9 | { 10 | this.reader = null; 11 | this.yytext = null; 12 | this.yyleng = 0; 13 | this.yylineno = 0; 14 | this.yylloc = 0; 15 | } 16 | 17 | Lexer.prototype.showPosition = function showPosition() 18 | { 19 | return (this.yylloc); 20 | }; 21 | 22 | /* 23 | * When we return something other than undefined: 24 | * 25 | * yytext is a Buffer containing only this token. 26 | * yyleng is yytext.length (for compatibility). 27 | * yylloc is the offset in the input buffer at which the token starts. 28 | */ 29 | Lexer.prototype.lex = function lex() 30 | { 31 | var tag = this.reader.peek(); 32 | var soff = this.reader.offset + 1; 33 | var lenlen = this.reader.readLength(soff) - soff; 34 | var len = this.reader.length; 35 | var token = undefined; 36 | var skip = 0; 37 | var token_len = 1 + lenlen + len; /* TLV */ 38 | var i; 39 | 40 | this.yylloc = this.reader.offset; 41 | this.yytext = undefined; 42 | this.yyleng = 0; 43 | 44 | switch (tag) { 45 | case ASN1.Integer: 46 | token = 'INTEGER'; 47 | break; 48 | case ASN1.OctetString: 49 | token = 'OCTET_STRING'; 50 | break; 51 | case ASN1.Null: 52 | token = 'NULL'; 53 | break; 54 | case ASN1.OID: 55 | token = 'OBJECT_IDENTIFIER'; 56 | break; 57 | case ASN1.Constructor | ASN1.Sequence: 58 | token = 'SEQUENCE'; 59 | skip = 1 + lenlen; 60 | token_len = 0; 61 | break; 62 | case 0x40 | 0x00: 63 | token = 'IP_ADDRESS'; 64 | break; 65 | case 0x40 | 0x03: 66 | token = 'TIME_TICKS'; 67 | break; 68 | case ASN1.Context | ASN1.Constructor | 0x00: 69 | case ASN1.Context | ASN1.Constructor | 0x01: 70 | case ASN1.Context | ASN1.Constructor | 0x02: 71 | case ASN1.Context | ASN1.Constructor | 0x03: 72 | case ASN1.Context | ASN1.Constructor | 0x04: 73 | case ASN1.Context | ASN1.Constructor | 0x05: 74 | case ASN1.Context | ASN1.Constructor | 0x06: 75 | case ASN1.Context | ASN1.Constructor | 0x07: 76 | case ASN1.Context | ASN1.Constructor | 0x08: 77 | token = 'CONTEXT_CONSTRUCTED_' + 78 | (tag & ~(ASN1.Context | ASN1.Constructor)); 79 | skip = 1 + lenlen; 80 | token_len = 0; 81 | break; 82 | case null: 83 | token = null; 84 | break; 85 | default: 86 | token = 'DATA'; 87 | break; 88 | } 89 | 90 | if (token_len > 0) { 91 | this.yytext = new Buffer(token_len); 92 | this.yyleng = token_len; 93 | this.reader.buffer.copy(this.yytext, 0, 0, token_len); 94 | skip = token_len; 95 | } 96 | 97 | for (i = 0; i < skip; i++) 98 | this.reader.readByte(); 99 | 100 | return (token); 101 | }; 102 | 103 | Lexer.prototype.setInput = function setInput(buffer) 104 | { 105 | this.buffer = buffer; 106 | this.reader = new ASN1.Reader(buffer); 107 | }; 108 | 109 | module.exports = Lexer; 110 | -------------------------------------------------------------------------------- /lib/listener.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 3 | * Copyright (c) 2013 Joyent, Inc. All rights reserved. 4 | */ 5 | 6 | var util = require('util'); 7 | var Receiver = require('./receiver'); 8 | var PDU = require('./protocol/pdu'); 9 | 10 | function 11 | Listener(options) 12 | { 13 | Receiver.call(this, options); 14 | this._connections = []; 15 | } 16 | util.inherits(Listener, Receiver); 17 | 18 | Listener.prototype.bind = function bind(arg, cb) { 19 | var self = this; 20 | var conn; 21 | 22 | if (typeof (arg) !== 'object') 23 | throw new TypeError('arg (object) is required'); 24 | if (typeof (arg.family) !== 'string') 25 | throw new TypeError('arg.family (string) is required'); 26 | if (typeof (arg.port) !== 'number') 27 | throw new TypeError('arg.port (number) is required'); 28 | if (typeof (arg.addr) !== 'undefined' && 29 | typeof (arg.addr) !== 'string') 30 | throw new TypeError('arg.addr must be a string'); 31 | 32 | if (typeof (cb) !== 'undefined' && typeof (cb) !== 'function') 33 | throw new TypeError('cb must be a function'); 34 | 35 | conn = this.createSocket(arg.family); 36 | this._connections.push(conn); 37 | 38 | conn.on('listening', function () { 39 | self._log.info('Bound to ' + conn.address().address + ':' + 40 | conn.address().port); 41 | if (cb) 42 | cb(); 43 | }); 44 | 45 | conn.bind(arg.port, arg.addr); 46 | }; 47 | 48 | Listener.prototype.close = function close() { 49 | var self = this; 50 | 51 | this._connections.forEach(function (c) { 52 | self._log.info('Shutting down endpoint ' + c.address().address + 53 | ':' + c.address().port); 54 | c.close(); 55 | }); 56 | }; 57 | 58 | module.exports = Listener; 59 | -------------------------------------------------------------------------------- /lib/mib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var assert = require('assert'); 6 | var util = require('util'); 7 | var data = require('./protocol/data'); 8 | 9 | function 10 | MIBNode(addr, parent) 11 | { 12 | var self = this; 13 | 14 | if (typeof (addr) !== 'object' || !util.isArray(addr)) 15 | throw new TypeError('addr (array) is required'); 16 | 17 | this._addr = addr; 18 | this._oid = this._addr.join('.'); 19 | this._children = []; 20 | this._parent = parent; 21 | this._decor = {}; 22 | 23 | this.__defineGetter__('oid', function () { return (self._oid); }); 24 | this.__defineGetter__('addr', function () { return (self._addr); }); 25 | this.__defineGetter__('parent', function () { return (self._parent); }); 26 | } 27 | 28 | MIBNode.prototype.child = function child(idx) { 29 | if (typeof (idx) !== 'number') 30 | throw new TypeError('idx (number) is required'); 31 | 32 | return (this._children[idx]); 33 | }; 34 | 35 | MIBNode.prototype.decor = function (tag) { 36 | return (this._decor[tag]); 37 | }; 38 | 39 | MIBNode.prototype.decorate = function (arg) { 40 | if (typeof (arg) !== 'object') 41 | throw new TypeError('arg (object) is required'); 42 | if (typeof (arg.tag) !== 'string') 43 | throw new TypeError('arg.tag (string) is required'); 44 | 45 | this._decor[arg.tag] = arg.obj; 46 | }; 47 | 48 | MIBNode.prototype.listChildren = function listChildren(lowest) { 49 | var sorted = []; 50 | 51 | if (typeof (lowest) === 'undefined') 52 | lowest = 0; 53 | 54 | this._children.forEach(function (c, i) { 55 | if (i >= lowest) 56 | sorted.push(i); 57 | }); 58 | 59 | sorted.sort(function (a, b) { 60 | return (a - b); 61 | }); 62 | 63 | return (sorted); 64 | }; 65 | 66 | function 67 | oid_is_descended(oid, ancestor) 68 | { 69 | var a_addr = data.canonicalizeOID(ancestor); 70 | var addr = data.canonicalizeOID(oid); 71 | var is_a = true; 72 | 73 | if (addr.length <= a_addr.length) 74 | return (false); 75 | 76 | a_addr.forEach(function (o, i) { 77 | if (addr[i] !== a_addr[i]) 78 | is_a = false; 79 | }); 80 | 81 | return (is_a); 82 | } 83 | 84 | MIBNode.prototype.isDescendant = function isDescendant(oid) { 85 | return (oid_is_descended(this._addr, oid)); 86 | }; 87 | 88 | MIBNode.prototype.isAncestor = function isAncestor(oid) { 89 | return (oid_is_descended(oid, this._addr)); 90 | }; 91 | 92 | function 93 | MIB() 94 | { 95 | this._root = new MIBNode([], null); 96 | } 97 | 98 | MIB.prototype.add = function add(def) { 99 | var addr; 100 | var node; 101 | var i; 102 | 103 | if (typeof (def) === 'string' || 104 | typeof (def) === 'object' && util.isArray(def)) 105 | addr = def; 106 | else 107 | addr = def.oid; 108 | 109 | addr = data.canonicalizeOID(addr); 110 | node = this._root; 111 | 112 | for (i = 0; i < addr.length; i++) { 113 | if (!node._children.hasOwnProperty(addr[i])) { 114 | node._children[addr[i]] = 115 | new MIBNode(addr.slice(0, i + 1), node); 116 | } 117 | node = node._children[addr[i]]; 118 | } 119 | 120 | return (node); 121 | }; 122 | 123 | MIB.prototype.lookup = function lookup(addr) { 124 | var i, node; 125 | 126 | addr = data.canonicalizeOID(addr); 127 | node = this._root; 128 | for (i = 0; i < addr.length; i++) { 129 | if (!node._children.hasOwnProperty(addr[i])) 130 | break; 131 | node = node._children[addr[i]]; 132 | } 133 | 134 | return (node); 135 | }; 136 | 137 | MIB.prototype.next_match = function (arg) { 138 | var child_indices; 139 | var sub; 140 | var i; 141 | 142 | if (typeof (arg) !== 'object') 143 | throw new TypeError('arg (object) is required'); 144 | if (typeof (arg.node) !== 'object' || !(arg.node instanceof MIBNode)) 145 | throw new TypeError('arg.node (object) is required'); 146 | if (typeof (arg.match) !== 'function') 147 | throw new TypeError('arg.match (function) is required'); 148 | if (typeof (arg.start) !== 'undefined' && 149 | typeof (arg.start) !== 'number') 150 | throw new TypeError('arg.start must be a number'); 151 | 152 | if (arg.match(arg.node) === true) 153 | return (arg.node); 154 | 155 | child_indices = arg.node.listChildren(arg.start); 156 | for (i = 0; i < child_indices.length; i++) { 157 | sub = this.next_match({ 158 | node: arg.node._children[child_indices[i]], 159 | match: arg.match 160 | }); 161 | if (sub) 162 | return (sub); 163 | } 164 | if (!arg.node._parent) 165 | return (null); 166 | 167 | return (this.next_match({ 168 | node: arg.node._parent, 169 | match: arg.match, 170 | start: arg.node._addr[arg.node._addr.length - 1] + 1 171 | })); 172 | }; 173 | 174 | module.exports = MIB; 175 | -------------------------------------------------------------------------------- /lib/mib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var mib2_system = require('./mib-2/system'); 6 | 7 | module.exports = function () { 8 | var providers = []; 9 | 10 | providers = providers.concat(mib2_system); 11 | 12 | return (providers); 13 | }(); 14 | -------------------------------------------------------------------------------- /lib/mib/mib-2/system.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var os = require('os'); 6 | var snmp = require('../../index.js'); 7 | 8 | function 9 | mib2_system_sysDescr(prq) 10 | { 11 | /* XXX configuration */ 12 | var val = snmp.data.createData({ type: 'OctetString', 13 | value: 'node-snmpjs' }); 14 | snmp.provider.readOnlyScalar(prq, val); 15 | } 16 | 17 | function 18 | mib2_system_sysObjectID(prq) 19 | { 20 | var sun = '.1.3.6.1.4.1.42'; 21 | var data = { 22 | type: 'ObjectIdentifier', 23 | value: sun + '.2.2.5' 24 | }; 25 | var val = snmp.data.createData(data); 26 | snmp.provider.readOnlyScalar(prq, val); 27 | } 28 | 29 | function 30 | mib2_system_sysUpTime(prq) 31 | { 32 | /* XXX introspection */ 33 | var val = snmp.data.createData({ type: 'TimeTicks', value: 0 }); 34 | snmp.provider.readOnlyScalar(prq, val); 35 | } 36 | 37 | function 38 | mib2_system_sysContact(prq) 39 | { 40 | /* XXX configuration */ 41 | var val = snmp.data.createData({ type: 'OctetString', 42 | value: 'Richard Nixon, trickydick@whitehouse.gov' }); 43 | snmp.provider.readOnlyScalar(prq, val); 44 | } 45 | 46 | function 47 | mib2_system_sysName(prq) 48 | { 49 | var nodename = os.hostname(); 50 | var val = snmp.data.createData({ type: 'OctetString', 51 | value: nodename }); 52 | snmp.provider.readOnlyScalar(prq, val); 53 | } 54 | 55 | function 56 | mib2_system_sysLocation(prq) 57 | { 58 | /* XXX configuration */ 59 | var val = snmp.data.createData({ type: 'OctetString', 60 | value: 'home' }); 61 | snmp.provider.readOnlyScalar(prq, val); 62 | } 63 | 64 | function 65 | mib2_system_sysServices(prq) 66 | { 67 | /* XXX configuration */ 68 | var val = snmp.data.createData({ type: 'Integer', value: 72 }); 69 | snmp.provider.readOnlyScalar(prq, val); 70 | } 71 | 72 | var system = [ 73 | { 74 | oid: '.1.3.6.1.2.1.1.1', 75 | handler: mib2_system_sysDescr 76 | }, 77 | { 78 | oid: '.1.3.6.1.2.1.1.2', 79 | handler: mib2_system_sysObjectID 80 | }, 81 | { 82 | oid: '.1.3.6.1.2.1.1.3', 83 | handler: mib2_system_sysUpTime 84 | }, 85 | { 86 | oid: '.1.3.6.1.2.1.1.4', 87 | handler: mib2_system_sysContact 88 | }, 89 | { 90 | oid: '.1.3.6.1.2.1.1.5', 91 | handler: mib2_system_sysName 92 | }, 93 | { 94 | oid: '.1.3.6.1.2.1.1.6', 95 | handler: mib2_system_sysLocation 96 | }, 97 | { 98 | oid: '.1.3.6.1.2.1.1.7', 99 | handler: mib2_system_sysServices 100 | } ]; 101 | 102 | module.exports = system; 103 | -------------------------------------------------------------------------------- /lib/protocol/data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var util = require('util'); 6 | var assert = require('assert'); 7 | var ASN1 = require('asn1').Ber; 8 | var TypeConflictError = require('../errors/varbind').TypeConflictError; 9 | var uint64_t = require('./uint64_t.js'); 10 | 11 | var MIN_INT32 = -2147483648; 12 | var MAX_INT32 = 2147483647; 13 | var MAX_UINT32 = 4294967295; 14 | 15 | var ERRORS = { 16 | noSuchObject: 0, 17 | noSuchInstance: 1, 18 | endOfMibView: 2 19 | }; 20 | 21 | var tag_to_type = {}; 22 | var type_to_obj = {}; 23 | 24 | function 25 | registerType(arg) 26 | { 27 | if (typeof (arg) !== 'object') 28 | throw new TypeError('arg (required) must be an object'); 29 | if (!arg.type || typeof (arg.type) !== 'string') 30 | throw new TypeError('arg.type (required) must be a string'); 31 | if (!arg.tag && !arg.f) { 32 | throw new TypeError('at least one of arg.tag and arg.f ' + 33 | ' is required'); 34 | } 35 | if (arg.tag && typeof (arg.tag) !== 'number') 36 | throw new TypeError('arg.tag must be an integer'); 37 | if (arg.tag % 1 !== 0) 38 | throw new TypeError('arg.tag must be an integer'); 39 | if (arg.tag < 0 || arg.tag > 255) 40 | throw new RangeError('arg.tag must be in [0, 255]'); 41 | if (arg.f && typeof (arg.f) !== 'function') 42 | throw new TypeError('arg.f must be a function'); 43 | 44 | if (arg.tag && !type_to_obj[arg.type] && !arg.f) { 45 | throw new TypeConflictError('type ' + arg.type + 46 | ' has no constructor registered'); 47 | } 48 | if (arg.tag && arg.f && tag_to_type[arg.tag] && 49 | tag_to_type[arg.tag] != arg.type) { 50 | throw new TypeConflictError('tag ' + arg.tag + 51 | ' is already registered to type ' + tag_to_type[arg.tag]); 52 | } 53 | if (arg.f && type_to_obj[arg.type] && type_to_obj[arg.type] != arg.f) { 54 | throw new TypeConflictError('type ' + arg.type + 55 | ' is already registered to a constructor'); 56 | } 57 | 58 | if (arg.tag) 59 | tag_to_type[arg.tag] = arg.type; 60 | if (arg.f) 61 | type_to_obj[arg.type] = arg.f; 62 | } 63 | 64 | function 65 | isTagRegistered(tag) { 66 | if (typeof (tag) !== 'number') 67 | throw new TypeError('tag (number) is required'); 68 | if (tag % 1 !== 0) 69 | throw new TypeError('tag must be an integer'); 70 | if (tag < 0 || tag > 255) 71 | throw new RangeError('tag must be in [0, 255]'); 72 | 73 | return (tag_to_type[tag] ? true : false); 74 | } 75 | 76 | function 77 | isTypeRegistered(type) { 78 | if (typeof (type) !== 'string') 79 | throw new TypeError('type (string) is required'); 80 | 81 | return (type_to_obj[type] ? true : false); 82 | } 83 | 84 | function 85 | canonicalizeOID(oid) 86 | { 87 | var addr, canon; 88 | var i; 89 | 90 | if (typeof (oid) === 'object' && util.isArray(oid)) { 91 | addr = oid; 92 | } else if (typeof (oid) === 'string') { 93 | addr = oid.split('.'); 94 | } else { 95 | throw new TypeError('oid (string or array) is required'); 96 | } 97 | 98 | if (addr.length < 3) 99 | throw new RangeError('object identifier is too short'); 100 | 101 | canon = []; 102 | for (i = 0; i < addr.length; i++) { 103 | var n; 104 | 105 | if (addr[i] === '') /* skip empty components */ 106 | continue; 107 | 108 | /* 109 | * Number(x) will convert these to 1 and 0, but they're not 110 | * legal in an OID. All other bogus values will result in 111 | * NaN which we check below. 112 | */ 113 | if (addr[i] === true || addr[i] === false) { 114 | throw new TypeError('object identifier component ' + 115 | addr[i] + ' is malformed'); 116 | } 117 | 118 | n = Number(addr[i]); 119 | 120 | if (isNaN(n)) { 121 | throw new TypeError('object identifier component ' + 122 | addr[i] + ' is malformed'); 123 | } 124 | if (n % 1 !== 0) { 125 | throw new TypeError('object identifier component ' + 126 | addr[i] + ' is not an integer'); 127 | } 128 | if (i === 0 && n > 2) { 129 | throw new RangeError('object identifier does not ' + 130 | 'begin with 0, 1, or 2'); 131 | } 132 | if (i === 1 && n > 39) { 133 | throw new RangeError('object identifier second ' + 134 | 'component ' + n + ' exceeds encoding limit of 39'); 135 | } 136 | if (n < 0) { 137 | throw new RangeError('object identifier component ' + 138 | addr[i] + ' is negative'); 139 | } 140 | if (n > MAX_INT32) { 141 | throw new RangeError('object identifier component ' + 142 | addr[i] + ' is too large'); 143 | } 144 | canon.push(n); 145 | } 146 | 147 | return (canon); 148 | } 149 | 150 | function 151 | SnmpData() 152 | { 153 | var self = this; 154 | 155 | this._value = undefined; 156 | 157 | this.__defineGetter__('typename', function () { 158 | return (self._typename); 159 | }); 160 | this.__defineGetter__('tag', function () { 161 | return (self._tag); 162 | }); 163 | this.__defineGetter__('value', function () { 164 | return (self._value); 165 | }); 166 | this.__defineSetter__('value', function (v) { 167 | throw new TypeError('Cannot set untyped value'); 168 | }); 169 | } 170 | 171 | SnmpData.prototype._tag = 0x100; 172 | SnmpData.prototype._typename = '__snmpjs_InvalidType'; 173 | SnmpData.prototype.__snmpjs_magic = 'SnmpData'; 174 | 175 | SnmpData.prototype.encode = function _encode(writer) { 176 | if (typeof (this._value) === 'undefined') 177 | throw new TypeError('Cannot encode undefined value'); 178 | 179 | throw new TypeError('Cannot encode untyped data'); 180 | }; 181 | 182 | SnmpData.prototype.clone = function _clone() { 183 | var clone = new this.constructor(this._value); 184 | 185 | clone._tag = this._tag; 186 | clone._typename = this._typename; 187 | 188 | return (clone); 189 | }; 190 | 191 | function 192 | SnmpInteger(value) 193 | { 194 | var self = this; 195 | 196 | SnmpData.call(this); 197 | 198 | this.__defineSetter__('value', function (v) { 199 | if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) { 200 | self._tag = v.peek(); 201 | v = v._readTag(self._tag); 202 | } 203 | if (typeof (v) !== 'number' || v % 1 !== 0) 204 | throw new TypeError('value is of incompatible type'); 205 | if (v < MIN_INT32 || v > MAX_INT32) { 206 | throw new RangeError('value ' + v + 207 | ' out of range for ' + self._typename); 208 | } 209 | self._value = v >> 0; 210 | }); 211 | 212 | if (value !== undefined) 213 | this.value = value; 214 | } 215 | util.inherits(SnmpInteger, SnmpData); 216 | 217 | SnmpInteger.prototype._tag = ASN1.Integer; 218 | SnmpInteger.prototype._typename = 'Integer'; 219 | 220 | SnmpInteger.prototype.encode = function _integer_encode(writer) { 221 | writer.writeInt(this._value, this._tag); 222 | }; 223 | 224 | function 225 | SnmpOctetString(value) 226 | { 227 | var self = this; 228 | 229 | SnmpData.call(this); 230 | 231 | this.__defineSetter__('value', function (v) { 232 | if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) { 233 | var b; 234 | self._tag = v.peek(); 235 | b = v.readString(self._tag, true); 236 | v = new Buffer(b.length); 237 | b.copy(v); 238 | self._value = b; 239 | } else if (Buffer.isBuffer(v)) { 240 | self._value = new Buffer(v.length); 241 | v.copy(self._value); 242 | } else if (typeof (v) === 'string') { 243 | self._value = new Buffer(v, 'ascii'); 244 | } else { 245 | throw new TypeError('value is of incompatible type'); 246 | } 247 | }); 248 | 249 | if (value !== undefined) 250 | this.value = value; 251 | } 252 | util.inherits(SnmpOctetString, SnmpData); 253 | 254 | SnmpOctetString.prototype._tag = ASN1.OctetString; 255 | SnmpOctetString.prototype._typename = 'OctetString'; 256 | 257 | SnmpOctetString.prototype.encode = function _octetstring_encode(writer) { 258 | writer.writeBuffer(this._value, this._tag); 259 | }; 260 | 261 | function 262 | SnmpOID(value) 263 | { 264 | var self = this; 265 | 266 | SnmpData.call(this); 267 | 268 | this.__defineSetter__('value', function (v) { 269 | if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) { 270 | self._tag = v.peek(); 271 | v = v.readOID(self._tag); 272 | } 273 | if (typeof (v) !== 'string' && 274 | (typeof (v) !== 'object' || !util.isArray(v))) 275 | throw new TypeError('value is of incompatible type'); 276 | self._value = canonicalizeOID(v).join('.'); 277 | }); 278 | 279 | if (value !== undefined) 280 | this.value = value; 281 | } 282 | util.inherits(SnmpOID, SnmpData); 283 | 284 | SnmpOID.prototype._tag = ASN1.OID; 285 | SnmpOID.prototype._typename = 'ObjectIdentifier'; 286 | 287 | SnmpOID.prototype.encode = function _oid_encode(writer) { 288 | writer.writeOID(this._value, this._tag); 289 | }; 290 | 291 | function 292 | SnmpIpAddress(value) 293 | { 294 | var self = this; 295 | 296 | SnmpData.call(this); 297 | 298 | this.__defineSetter__('value', function (v) { 299 | if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) { 300 | var buf, str; 301 | 302 | self._tag = v.peek(); 303 | buf = v.readString(self._tag, true); 304 | if (buf.length != 4) { 305 | throw new RangeError('address length ' + 306 | buf.length + ' is incorrect'); 307 | } 308 | str = buf.readUInt8(0) + '.' + 309 | buf.readUInt8(1) + '.' + 310 | buf.readUInt8(2) + '.' + 311 | buf.readUInt8(3); 312 | v = str; 313 | } 314 | if (typeof (v) !== 'string') 315 | throw new TypeError('value is of incompatible type'); 316 | 317 | var o = v.split('.'); 318 | var i; 319 | 320 | if (o.length != 4) 321 | throw new TypeError('address ' + v + ' is malformed'); 322 | 323 | for (i = 0; i < 4; i++) { 324 | var n = Number(o[i]); 325 | 326 | if (isNaN(n) || n % 1 != 0) { 327 | throw new TypeError('component ' + o[i] + 328 | ' is not an integer'); 329 | } 330 | if (o[i] < 0 || o[i] > 255) { 331 | throw new RangeError('component ' + o[i] + 332 | ' is out of range'); 333 | } 334 | } 335 | 336 | self._value = v; 337 | }); 338 | 339 | if (value !== undefined) 340 | this.value = value; 341 | } 342 | util.inherits(SnmpIpAddress, SnmpData); 343 | 344 | SnmpIpAddress.prototype._tag = 0x40 | 0x00; 345 | SnmpIpAddress.prototype._typename = 'IpAddress'; 346 | 347 | SnmpIpAddress.prototype.encode = function _ipaddress_encode(writer) { 348 | var o = this._value.split('.'); 349 | var buf = new Buffer(4); 350 | 351 | buf[0] = Number(o[0]); 352 | buf[1] = Number(o[1]); 353 | buf[2] = Number(o[2]); 354 | buf[3] = Number(o[3]); 355 | 356 | writer.writeBuffer(buf, this._tag); 357 | }; 358 | 359 | function 360 | SnmpCounter32(value) 361 | { 362 | var self = this; 363 | 364 | SnmpData.call(this); 365 | 366 | this.__defineSetter__('value', function (v) { 367 | if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) { 368 | var x, len, soff, lenlen; 369 | 370 | self._tag = v.readByte(); 371 | soff = v.offset; 372 | lenlen = v.readLength() - soff; 373 | len = v.length; 374 | v.readByte(); /* Consume length byte */ 375 | /* 376 | * ASN.1 BER 8.3. The standard allows integer encodings 377 | * to have the first octet contain all zeros as long as 378 | * the high bit of the second octet is set. While this 379 | * seems silly when there are 5 total octets for an 380 | * integer type that is constrained to 32 bits, it's 381 | * not technically illegal. If we hit this, just ignore 382 | * the first all-zero byte. 383 | * 384 | * Note that we need not handle the case where the first 385 | * byte is all 1s; that encoding would also have the 386 | * high bit of the second octet set, which is illegal. 387 | */ 388 | if (len === 5) { 389 | x = v.readByte(); 390 | if (x === 0) 391 | --len; 392 | } 393 | if (lenlen > 1 || len > 4) 394 | throw new RangeError('integer is too long'); 395 | 396 | x = 0; 397 | for (; len > 0; len--) { 398 | x <<= 8; 399 | x |= v.readByte(); 400 | } 401 | v = x >>> 0; 402 | } 403 | if (typeof (v) !== 'number') 404 | throw new TypeError('value is not a number'); 405 | if (v % 1 !== 0) 406 | throw new TypeError('value is not an integer'); 407 | if (v < 0 || v > MAX_UINT32) { 408 | throw new RangeError('value ' + v + 409 | ' out of range for ' + self._typename); 410 | } 411 | 412 | self._value = v; 413 | }); 414 | 415 | if (value !== undefined) 416 | this.value = value; 417 | } 418 | util.inherits(SnmpCounter32, SnmpData); 419 | 420 | SnmpCounter32.prototype._tag = 0x40 | 0x01; 421 | SnmpCounter32.prototype._typename = 'Counter32'; 422 | 423 | SnmpCounter32.prototype.encode = function _counter32_encode(writer) { 424 | var bytes = []; 425 | var v = this._value; 426 | 427 | writer.writeByte(this._tag); 428 | 429 | do { 430 | bytes.unshift(v & 0xff); 431 | v = v >>> 8; 432 | } while (v != 0); 433 | 434 | assert.ok(bytes.length <= 4); 435 | writer.writeByte(bytes.length); 436 | 437 | for (v = 0; v < bytes.length; v++) 438 | writer.writeByte(bytes[v]); 439 | }; 440 | 441 | function 442 | SnmpUnsigned32(value) 443 | { 444 | SnmpCounter32.call(this); 445 | 446 | if (value !== undefined) 447 | this.value = value; 448 | } 449 | util.inherits(SnmpUnsigned32, SnmpCounter32); 450 | 451 | SnmpUnsigned32.prototype._tag = 0x40 | 0x02; 452 | SnmpUnsigned32.prototype._typename = 'Unsigned32'; 453 | 454 | function 455 | SnmpTimeTicks(value) 456 | { 457 | SnmpCounter32.call(this); 458 | 459 | if (value !== undefined) 460 | this.value = value; 461 | } 462 | util.inherits(SnmpTimeTicks, SnmpCounter32); 463 | 464 | SnmpTimeTicks.prototype._tag = 0x40 | 0x03; 465 | SnmpTimeTicks.prototype._typename = 'TimeTicks'; 466 | 467 | function 468 | SnmpOpaque(value) 469 | { 470 | SnmpOctetString.call(this); 471 | 472 | if (value !== undefined) 473 | this.value = value; 474 | } 475 | util.inherits(SnmpOpaque, SnmpOctetString); 476 | 477 | SnmpOpaque.prototype._tag = 0x40 | 0x04; 478 | SnmpOpaque.prototype._typename = 'Opaque'; 479 | 480 | function 481 | SnmpCounter64(value) 482 | { 483 | var self = this; 484 | 485 | SnmpData.call(this); 486 | 487 | this.__defineSetter__('value', function (v) { 488 | if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) { 489 | var hi, lo, soff, len, lenlen; 490 | 491 | self._tag = v.readByte(); 492 | soff = v.offset; 493 | lenlen = v.readLength() - soff; 494 | len = v.length; 495 | v.readByte(); /* Consume length byte */ 496 | /* See analogous comment for Counter32. */ 497 | if (len === 9) { 498 | lo = v.readByte(); 499 | if (lo === 0) 500 | --len; 501 | } 502 | if (lenlen > 1 || len > 8) 503 | throw new RangeError('integer is too long'); 504 | hi = lo = 0; 505 | for (; len > 4; len--) { 506 | hi <<= 8; 507 | hi |= v.readByte(); 508 | } 509 | for (; len > 0; len--) { 510 | lo <<= 8; 511 | lo |= v.readByte(); 512 | } 513 | self._value = new uint64_t(hi, lo); 514 | } else if (typeof (v) === 'number') { 515 | if (v % 1 !== 0) 516 | throw new TypeError('value ' + v + 517 | ' is not an integer'); 518 | if (v < 0 || v > MAX_UINT32) 519 | throw new RangeError('value ' + v + 520 | ' is out of range or unrepresentable'); 521 | 522 | self._value = new uint64_t(0, v); 523 | } else if (typeof (v) === 'object') { 524 | if (!v.hasOwnProperty('lo') || !v.hasOwnProperty('hi')) 525 | throw new TypeError('value is missing ' + 526 | 'required lo and/or hi properties'); 527 | if (typeof (v.hi) !== 'number' || v.hi % 1 !== 0) 528 | throw new TypeError('v.hi is not an integer'); 529 | if (typeof (v.lo) !== 'number' || v.lo % 1 !== 0) 530 | throw new TypeError('v.lo is not an integer'); 531 | if (v.hi < 0 || v.hi > MAX_UINT32 || 532 | v.lo < 0 || v.lo > MAX_UINT32) 533 | throw new RangeError('one or both components ' + 534 | 'is out of representable range'); 535 | 536 | self._value = new uint64_t(v.hi, v.lo); 537 | } else { 538 | throw new TypeError('value is of incompatible type'); 539 | } 540 | }); 541 | 542 | if (value !== undefined) 543 | this.value = value; 544 | } 545 | util.inherits(SnmpCounter64, SnmpData); 546 | 547 | SnmpCounter64.prototype._tag = 0x40 | 0x06; 548 | SnmpCounter64.prototype._typename = 'Counter64'; 549 | 550 | SnmpCounter64.prototype.encode = function _counter64_encode(writer) { 551 | var bytes; 552 | var i; 553 | 554 | writer.writeByte(this._tag); 555 | 556 | bytes = this._value.toOctets(); 557 | assert.ok(bytes.length <= 8); 558 | writer.writeByte(bytes.length); 559 | 560 | for (i = 0; i < bytes.length; i++) 561 | writer.writeByte(bytes[i]); 562 | }; 563 | 564 | function 565 | SnmpNull(value) 566 | { 567 | var self = this; 568 | 569 | SnmpData.call(this); 570 | 571 | this.__defineSetter__('value', function (v) { 572 | if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) { 573 | self._tag = v.readByte(); 574 | if (v.readByte() !== 0) { 575 | throw new RangeError('Null value has nonzero ' + 576 | 'length'); 577 | } 578 | } else if (v === null) { 579 | self._value = null; 580 | return; 581 | } else if (typeof (v) === 'number') { 582 | if (v % 1 !== 0) { 583 | throw new TypeError('null value ' + v + 584 | ' is not an integer'); 585 | } 586 | if (v < 0 || v > 0x7f) { 587 | throw new RangeError('null value ' + v + 588 | ' is out of range [0, 0x7f]'); 589 | } 590 | self._tag = ASN1.Context | v; 591 | } else { 592 | throw new TypeError('value must be null or a number'); 593 | } 594 | switch (self._tag) { 595 | case ASN1.Context | ERRORS.noSuchObject: 596 | self._value = ERRORS.noSuchObject; 597 | break; 598 | case ASN1.Context | ERRORS.noSuchInstance: 599 | self._value = ERRORS.noSuchInstance; 600 | break; 601 | case ASN1.Context | ERRORS.endOfMibView: 602 | self._value = ERRORS.endOfMibView; 603 | break; 604 | case ASN1.Null: 605 | default: 606 | self._value = null; 607 | break; 608 | } 609 | }); 610 | 611 | if (value !== undefined) 612 | this.value = value; 613 | } 614 | util.inherits(SnmpNull, SnmpData); 615 | 616 | SnmpNull.prototype._tag = ASN1.Null; 617 | SnmpNull.prototype._typename = 'Null'; 618 | 619 | SnmpNull.prototype.encode = function _null_encode(writer) { 620 | if (this._value === null) { 621 | writer.writeNull(); 622 | } else { 623 | writer.writeByte(ASN1.Context | this._value); 624 | writer.writeByte(0x00); 625 | } 626 | }; 627 | 628 | function 629 | createData(arg) 630 | { 631 | var type, tag; 632 | var f; 633 | 634 | if (typeof (arg) !== 'object') 635 | throw new TypeError('arg (object) is required'); 636 | 637 | type = arg.type; 638 | 639 | if (typeof (type) === 'undefined') { 640 | if (typeof (arg.value) !== 'object' || 641 | !(arg.value instanceof ASN1.Reader)) { 642 | throw new TypeError('type (string) is required ' + 643 | 'when value is not an ASN.1 BER reader'); 644 | } 645 | tag = arg.value.peek(); 646 | type = tag_to_type[tag]; 647 | if (!type) { 648 | throw new ReferenceError('data type for ASN.1 tag ' + 649 | tag + ' unknown'); 650 | } 651 | } 652 | 653 | if (typeof (type) === 'number' && type % 1 === 0) { 654 | type = tag_to_type[type]; 655 | if (!type) { 656 | throw new ReferenceError('tag ' + arg.type + 657 | ' has no registered type'); 658 | } 659 | } else if (typeof (type) !== 'string') { 660 | throw new TypeError('type (string) is required'); 661 | } 662 | 663 | if (!(f = type_to_obj[type])) { 664 | throw new ReferenceError('data type ' + type + 665 | ' has no registered constructor'); 666 | } 667 | 668 | return (new f(arg.value)); 669 | } 670 | 671 | module.exports = function _data_init() { 672 | var data = { 673 | SnmpData: SnmpData, 674 | createData: createData, 675 | registerType: registerType, 676 | isTagRegistered: isTagRegistered, 677 | isTypeRegistered: isTypeRegistered, 678 | canonicalizeOID: canonicalizeOID 679 | }; 680 | 681 | var stock = [ 682 | SnmpInteger, 683 | SnmpOctetString, 684 | SnmpOID, 685 | SnmpIpAddress, 686 | SnmpCounter32, 687 | SnmpUnsigned32, 688 | SnmpTimeTicks, 689 | SnmpOpaque, 690 | SnmpCounter64, 691 | SnmpNull 692 | ]; 693 | 694 | stock.forEach(function (t) { 695 | registerType({ 696 | type: t.prototype._typename, 697 | tag: t.prototype._tag, 698 | f: t 699 | }); 700 | }); 701 | 702 | /* 703 | * These aliases exist so that we can encode inline varbind error status 704 | * markers. The specifications describe these as being of the NULL type 705 | * even though they have their own tags (which are in fact values more 706 | * than anything, since the value portion of these objects is empty). 707 | */ 708 | registerType({ 709 | type: 'Null', 710 | tag: ASN1.Context | ERRORS.noSuchObject 711 | }); 712 | registerType({ 713 | type: 'Null', 714 | tag: ASN1.Context | ERRORS.noSuchInstance 715 | }); 716 | registerType({ 717 | type: 'Null', 718 | tag: ASN1.Context | ERRORS.endOfMibView 719 | }); 720 | 721 | Object.keys(ERRORS).forEach(function (e) { 722 | data.__defineGetter__(e, function () { return (ERRORS[e]); }); 723 | }); 724 | 725 | data.isSnmpData = function (d) { 726 | return ((typeof (d.__snmpjs_magic) == 'string' && 727 | d.__snmpjs_magic === 'SnmpData') ? true : false); 728 | }; 729 | 730 | return (data); 731 | }(); 732 | -------------------------------------------------------------------------------- /lib/protocol/message.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var util = require('util'); 6 | var ASN1 = require('asn1').Ber; 7 | var lexer = require('../lexer'); 8 | var PDU = require('./pdu'); 9 | var varbind = require('./varbind'); 10 | var data = require('./data'); 11 | var parser = require('../parser').parser; 12 | var EmptyMessageError = require('../errors/message').EmptyMessageError; 13 | var MessageParseError = require('../errors/message').MessageParseError; 14 | var NoSupportError = require('../errors/message').NoSupportError; 15 | 16 | function 17 | _set_bind(msg, key, primitive, type) { 18 | return (function (v) { 19 | if (typeof (v) === primitive) { 20 | v = data.createData({ value: v, 21 | type: type }); 22 | } 23 | if (typeof (v) !== 'object' || !data.isSnmpData(v) || 24 | v.typename != type) { 25 | throw new TypeError(key + ' must be a ' + primitive + 26 | ' or SNMP data object of type ' + type); 27 | } 28 | 29 | msg[key] = v; 30 | }); 31 | } 32 | 33 | function 34 | SnmpMessage(arg) 35 | { 36 | var self = this; 37 | var version, community; 38 | 39 | if (typeof (arg) !== 'object') 40 | throw new TypeError('arg must be an object'); 41 | 42 | if (typeof (arg.version) === 'undefined') 43 | throw new TypeError('arg.version is required'); 44 | if (typeof (arg.version) === 'object' && data.isSnmpData(arg.version)) 45 | version = arg.version; 46 | else 47 | version = data.createData({ value: arg.version, 48 | type: 'Integer' }); 49 | if (typeof (version) !== 'object' || !data.isSnmpData(version) || 50 | version.typename != 'Integer') { 51 | throw new TypeError('arg.version must be an integer or ' + 52 | ' an SNMP data object of type Integer'); 53 | } 54 | 55 | if (typeof (arg.community) === 'undefined') 56 | throw new TypeError('arg.community is required'); 57 | if (typeof (arg.community) === 'object' && 58 | data.isSnmpData(arg.community)) 59 | community = arg.community; 60 | else 61 | community = data.createData({ value: arg.community, 62 | type: 'OctetString' }); 63 | if (typeof (community) !== 'object' || !data.isSnmpData(community) || 64 | community.typename != 'OctetString') { 65 | throw new TypeError('arg.community must be a string or ' + 66 | ' an SNMP data object of type OctetString'); 67 | } 68 | switch (version.value) { 69 | case 0: 70 | case 1: 71 | break; 72 | case 3: 73 | default: 74 | throw new NoSupportError('SNMPv3 is unsupported'); 75 | } 76 | 77 | this._version = version; 78 | this._community = community; 79 | this._raw = this._src = undefined; 80 | 81 | this.__defineGetter__('version', function () { 82 | return (self._version.value); 83 | }); 84 | this.__defineGetter__('community', function () { 85 | return (self._community.value); 86 | }); 87 | this.__defineGetter__('raw', function () { 88 | return (self._raw); 89 | }); 90 | this.__defineGetter__('src', function () { 91 | return (self._src); 92 | }); 93 | this.__defineGetter__('pdu', function () { 94 | return (self._pdu); 95 | }); 96 | this.__defineSetter__('pdu', function (v) { 97 | if (typeof (v) !== 'object' || !PDU.isSnmpPDU(v)) 98 | throw new TypeError('pdu must be an object'); 99 | 100 | self._pdu = v; 101 | }); 102 | 103 | if (arg.pdu) 104 | this.pdu = arg.pdu; 105 | } 106 | 107 | SnmpMessage.prototype.__snmpjs_magic = 'SnmpMessage'; 108 | 109 | SnmpMessage.prototype._setOrigin = function _setOrigin(raw, src) 110 | { 111 | this._raw = raw; 112 | this._src = src; 113 | }; 114 | 115 | SnmpMessage.prototype.encode = function encode() 116 | { 117 | var writer = new ASN1.Writer(); 118 | 119 | if (!this._community) 120 | throw new TypeError('Message is missing a community'); 121 | if (!this._pdu) 122 | throw new TypeError('Message contains no PDU'); 123 | if (this._raw) 124 | throw new TypeError('Message has already been encoded'); 125 | 126 | writer.startSequence(); 127 | this._version.encode(writer); 128 | this._community.encode(writer); 129 | this._pdu.encode(writer); 130 | writer.endSequence(); 131 | 132 | this._raw = { 133 | buf: writer.buffer, 134 | len: writer.buffer.length 135 | }; 136 | }; 137 | 138 | function 139 | ParseContext() 140 | { 141 | this.ASN1 = ASN1; 142 | this.pdu = PDU; 143 | this.varbind = varbind; 144 | this.data = data; 145 | this.message = module.exports; 146 | this.content = undefined; 147 | } 148 | 149 | ParseContext.prototype.parse = function parse(raw, src) 150 | { 151 | /* 152 | * This is vile. Unfortunately, the parser generated by Jison isn't an 153 | * object instance, nor is it anything that can construct one. This 154 | * doesn't really matter because we don't do anything asynchronous 155 | * during parsing, but it's still wrong. 156 | */ 157 | parser.yy = this; 158 | 159 | parser.parse(raw.buf); 160 | if (!this.content) 161 | throw new EmptyMessageError(); 162 | 163 | this.content._setOrigin(raw, src); 164 | return (this.content); 165 | }; 166 | 167 | ParseContext.prototype.parseError = function parseError(str, hash) 168 | { 169 | throw new MessageParseError(str, hash); 170 | }; 171 | 172 | ParseContext.prototype.setContent = function setContent(content) 173 | { 174 | this.content = content; 175 | }; 176 | 177 | function 178 | parseMessage(arg) 179 | { 180 | var ctx; 181 | 182 | if (typeof (arg) !== 'object') 183 | throw new TypeError('arg (object) is required'); 184 | if (typeof (arg.raw) !== 'object') 185 | throw new TypeError('arg.raw (object) is required'); 186 | if (Buffer.isBuffer(arg.raw)) { 187 | arg.raw = { 188 | buf: arg.raw, 189 | len: arg.raw.length 190 | }; 191 | } 192 | if (typeof (arg.raw.buf) !== 'object' || !Buffer.isBuffer(arg.raw.buf)) 193 | throw new TypeError('arg.raw does not contain a Buffer'); 194 | 195 | ctx = new ParseContext(); 196 | return (ctx.parse(arg.raw, arg.src)); 197 | } 198 | 199 | function 200 | createMessage(arg) 201 | { 202 | return (new SnmpMessage(arg)); 203 | } 204 | 205 | function 206 | strversion(ver) 207 | { 208 | if (typeof (ver) !== 'number') 209 | throw new TypeError('ver (number) is required'); 210 | switch (ver) { 211 | case 0: 212 | return ('v1(0)'); 213 | case 1: 214 | return ('v2c(1)'); 215 | case 3: 216 | return ('v3(3)'); 217 | default: 218 | return ('(' + ver + ')'); 219 | } 220 | } 221 | 222 | function 223 | bunyan_serialize_snmpmsg(snmpmsg) 224 | { 225 | var i; 226 | var obj = { 227 | version: strversion(snmpmsg.version), 228 | community: snmpmsg.community.toString() 229 | }; 230 | 231 | if (snmpmsg.pdu.op === PDU.Trap) { 232 | obj.pdu = { 233 | op: PDU.strop(snmpmsg.pdu.op), 234 | enterprise: snmpmsg.pdu.enterprise, 235 | agent_addr: snmpmsg.pdu.agent_addr, 236 | generic_trap: snmpmsg.pdu.generic_trap, 237 | specific_trap: snmpmsg.pdu.specific_trap, 238 | time_stamp: snmpmsg.pdu.time_stamp, 239 | varbinds: [] 240 | }; 241 | } else { 242 | obj.pdu = { 243 | op: PDU.strop(snmpmsg.pdu.op), 244 | request_id: snmpmsg.pdu.request_id, 245 | error_status: PDU.strerror(snmpmsg.pdu.error_status), 246 | error_index: snmpmsg.pdu.error_index, 247 | varbinds: [] 248 | }; 249 | } 250 | for (i = 0; i < snmpmsg.pdu.varbinds.length; i++) { 251 | var dv = snmpmsg.pdu.varbinds[i].data.value; 252 | var vb = { 253 | oid: snmpmsg.pdu.varbinds[i].oid, 254 | typename: snmpmsg.pdu.varbinds[i].data.typename, 255 | value: dv, 256 | string_value: dv.toString() 257 | }; 258 | obj.pdu.varbinds.push(vb); 259 | } 260 | 261 | return (obj); 262 | } 263 | 264 | module.exports = function _message_init() { 265 | var message = { 266 | parseMessage: parseMessage, 267 | createMessage: createMessage, 268 | strversion: strversion, 269 | serializer: bunyan_serialize_snmpmsg 270 | }; 271 | 272 | message.isSnmpMessage = function (m) { 273 | return ((typeof (m.__snmpjs_magic) === 'string' && 274 | m.__snmpjs_magic === 'SnmpMessage') ? true : false); 275 | }; 276 | 277 | parser.lexer = new lexer(); 278 | 279 | return (message); 280 | }(); 281 | -------------------------------------------------------------------------------- /lib/protocol/pdu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var util = require('util'); 6 | var ASN1 = require('asn1').Ber; 7 | var varbind = require('./varbind'); 8 | var data = require('./data'); 9 | 10 | var OPS = { 11 | GetRequest: 0, 12 | GetNextRequest: 1, 13 | Response: 2, 14 | SetRequest: 3, 15 | Trap: 4, /* OBSOLETE! */ 16 | GetBulkRequest: 5, 17 | InformRequest: 6, 18 | SNMPv2_Trap: 7, 19 | Report: 8 20 | }; 21 | 22 | var ERRORS = { 23 | noError: 0, 24 | tooBig: 1, 25 | noSuchName: 2, 26 | badValue: 3, 27 | readOnly: 4, 28 | genErr: 5, 29 | noAccess: 6, 30 | wrongType: 7, 31 | wrongLength: 8, 32 | wrongEncoding: 9, 33 | wrongValue: 10, 34 | noCreation: 11, 35 | inconsistentValue: 12, 36 | resourceUnavailable: 13, 37 | commitFailed: 14, 38 | undoFailed: 15, 39 | authorizationError: 16, 40 | notWritable: 17, 41 | inconsistentName: 18 42 | }; 43 | 44 | var TRAPS_V1 = { 45 | coldStart: 0, 46 | warmStart: 1, 47 | linkDown: 2, 48 | linkUp: 3, 49 | authenticationFailure: 4, 50 | egpNeighborLoss: 5, 51 | enterpriseSpecific: 6 52 | }; 53 | 54 | function 55 | SnmpPDU(arg) 56 | { 57 | var self = this; 58 | 59 | if (typeof (arg) !== 'object') 60 | throw new TypeError('arg (object) is required'); 61 | 62 | if (typeof (arg.op) !== 'number') 63 | throw new TypeError('arg.op (number) is required'); 64 | if (arg.op < 0 || arg.op > 31) /* ASN.1 limitation */ 65 | throw new RangeError('op ' + arg.op + ' is out of range'); 66 | 67 | this._op = arg.op; 68 | this._varbinds = []; 69 | 70 | this.__defineGetter__('op', function () { 71 | return (self._op); 72 | }); 73 | this.__defineGetter__('varbinds', function () { 74 | return (self._varbinds); 75 | }); 76 | this.__defineSetter__('varbinds', function (v) { 77 | var i = 0; 78 | 79 | if (typeof (v) === 'object' && varbind.isSnmpVarbind(v)) { 80 | self._varbinds = [ v ]; 81 | return; 82 | } 83 | 84 | if (typeof (v) !== 'object' || !util.isArray(v)) 85 | throw new TypeError('varbinds must be an array'); 86 | 87 | for (i = 0; i < v.length; i++) { 88 | if (typeof (v[i]) !== 'object' || 89 | !varbind.isSnmpVarbind(v[i])) { 90 | throw new TypeError('varbinds[' + i + '] is ' + 91 | 'of incompatible type'); 92 | } 93 | } 94 | self._varbinds = v; 95 | }); 96 | 97 | if (arg.varbinds) 98 | this.varbinds = arg.varbinds; 99 | } 100 | 101 | SnmpPDU.prototype.__snmpjs_magic = 'SnmpPDU'; 102 | 103 | SnmpPDU.prototype.clone = function () { 104 | throw new TypeError('Cannot clone base PDU object'); 105 | }; 106 | 107 | SnmpPDU.prototype.cloneAs = function (op) { 108 | throw new TypeError('Cannot clone base PDU object'); 109 | }; 110 | 111 | SnmpPDU.prototype.encode = function (writer) { 112 | throw new TypeError('Cannot encode base PDU object'); 113 | }; 114 | 115 | function 116 | _set_bind(pdu, key, primitive, type) { 117 | return (function (v) { 118 | if (typeof (v) === primitive) { 119 | v = data.createData({ value: v, 120 | type: type }); 121 | } 122 | if (typeof (v) !== 'object' || !data.isSnmpData(v) || 123 | v.typename != type) { 124 | throw new TypeError(key + ' must be a ' + primitive + 125 | ' or SNMP data object of type ' + type); 126 | } 127 | 128 | pdu[key] = v; 129 | }); 130 | } 131 | 132 | function 133 | SnmpStdPDU(arg) 134 | { 135 | var self = this; 136 | var request_id; 137 | var f; 138 | var errst_if_name; 139 | var erridx_if_name; 140 | 141 | SnmpPDU.call(this, arg); 142 | 143 | if (this._op === SnmpPDU.Trap) 144 | throw new TypeError('cannot create standard PDU as v1 trap'); 145 | 146 | if (typeof (arg.request_id) === 'undefined') 147 | throw new TypeError('arg.request_id is required'); 148 | if (typeof (arg.request_id) === 'number') { 149 | request_id = data.createData({ value: arg.request_id, 150 | type: 'Integer' }); 151 | } else { 152 | request_id = arg.request_id; 153 | } 154 | if (typeof (request_id) !== 'object' || !data.isSnmpData(request_id) || 155 | request_id.typename != 'Integer') { 156 | throw new TypeError('arg.request_id must be integer or ' + 157 | ' and SNMP data object of type Integer'); 158 | } 159 | 160 | this._request_id = request_id; 161 | this._error_status = data.createData({ value: 0, type: 'Integer' }); 162 | this._error_index = data.createData({ value: 0, type: 'Integer' }); 163 | 164 | this.__defineGetter__('request_id', function () { 165 | return (self._request_id.value); 166 | }); 167 | 168 | /* 169 | * We cheat a little here, taking advantage of the standard's very 170 | * deliberate use of the same structure for all PDU types. The 171 | * interpretation of these two values are different for GetBulk 172 | * requests, but they are otherwise the same so we will avoid creating 173 | * yet another PDU type and just present the appropriate names as 174 | * getters and setters. 175 | */ 176 | if (this.op == OPS.GetBulkRequest) { 177 | errst_if_name = 'non_repeaters'; 178 | erridx_if_name = 'max_repetitions'; 179 | } else { 180 | errst_if_name = 'error_status'; 181 | erridx_if_name = 'error_index'; 182 | } 183 | 184 | this.__defineGetter__(errst_if_name, function () { 185 | return (self._error_status.value); 186 | }); 187 | this.__defineSetter__(errst_if_name, 188 | _set_bind(self, '_error_status', 'number', 'Integer')); 189 | this.__defineGetter__(erridx_if_name, function () { 190 | return (self._error_index.value); 191 | }); 192 | f = _set_bind(self, '_error_index', 'number', 'Integer'); 193 | if (this.op == OPS.GetBulkRequest) { 194 | this.__defineSetter__(erridx_if_name, f); 195 | } else { 196 | this.__defineSetter__(erridx_if_name, function (v) { 197 | if (v < 0 || v > self._varbinds.length) { 198 | throw new RangeError('error index ' + v + 199 | ' is out of range'); 200 | } 201 | f(v); 202 | }); 203 | } 204 | } 205 | util.inherits(SnmpStdPDU, SnmpPDU); 206 | 207 | SnmpStdPDU.prototype.cloneAs = function (op) { 208 | var clone = new this.constructor({ op: op, 209 | request_id: this._request_id }); 210 | var i; 211 | 212 | clone._error_status = this._error_status; 213 | clone._error_index = this._error_index; 214 | for (i = 0; i < this._varbinds.length; i++) 215 | clone.varbinds.push(this._varbinds[i].clone()); 216 | 217 | return (clone); 218 | }; 219 | 220 | SnmpStdPDU.prototype.clone = function () { 221 | return (this.cloneAs(this._op)); 222 | }; 223 | 224 | SnmpStdPDU.prototype.encode = function (writer) { 225 | var i; 226 | 227 | if (this._op == OPS.GetBulkRequest && 228 | this._varbinds.length < this.non_repeaters) { 229 | throw new RangeError('number of non-repeater varbinds is ' + 230 | 'greater than the total varbind count'); 231 | } 232 | 233 | writer.startSequence(ASN1.Context | ASN1.Constructor | this._op); 234 | this._request_id.encode(writer); 235 | this._error_status.encode(writer); 236 | this._error_index.encode(writer); 237 | 238 | writer.startSequence(); 239 | for (i = 0; i < this._varbinds.length; i++) 240 | this._varbinds[i].encode(writer); 241 | writer.endSequence(); 242 | writer.endSequence(); 243 | }; 244 | 245 | function 246 | SnmpTrapV1PDU(arg) 247 | { 248 | var self = this; 249 | 250 | SnmpPDU.call(this, arg); 251 | 252 | this.__defineGetter__('enterprise', function () { 253 | if (self._enterprise) 254 | return (self._enterprise.value); 255 | return (undefined); 256 | }); 257 | this.__defineSetter__('enterprise', 258 | _set_bind(self, '_enterprise', 'string', 'ObjectIdentifier')); 259 | this.__defineGetter__('agent_addr', function () { 260 | if (self._agent_addr) 261 | return (self._agent_addr.value); 262 | return (undefined); 263 | }); 264 | this.__defineSetter__('agent_addr', 265 | _set_bind(self, '_agent_addr', 'string', 'IpAddress')); 266 | this.__defineGetter__('generic_trap', function () { 267 | if (self._generic_trap) 268 | return (self._generic_trap.value); 269 | return (undefined); 270 | }); 271 | this.__defineSetter__('generic_trap', 272 | _set_bind(self, '_generic_trap', 'number', 'Integer')); 273 | this.__defineGetter__('specific_trap', function () { 274 | if (self._specific_trap) 275 | return (self._specific_trap.value); 276 | return (undefined); 277 | }); 278 | this.__defineSetter__('specific_trap', 279 | _set_bind(self, '_specific_trap', 'number', 'Integer')); 280 | this.__defineGetter__('time_stamp', function () { 281 | if (self._time_stamp) 282 | return (self._time_stamp.value); 283 | return (undefined); 284 | }); 285 | this.__defineSetter__('time_stamp', 286 | _set_bind(self, '_time_stamp', 'number', 'TimeTicks')); 287 | 288 | if (arg.enterprise) 289 | this.enterprise = arg.enterprise; 290 | if (arg.agent_addr) 291 | this.agent_addr = arg.agent_addr; 292 | if (arg.generic_trap) 293 | this.generic_trap = arg.generic_trap; 294 | if (arg.specific_trap) 295 | this.specific_trap = arg.specific_trap; 296 | if (arg.time_stamp) 297 | this.time_stamp = arg.time_stamp; 298 | } 299 | util.inherits(SnmpTrapV1PDU, SnmpPDU); 300 | 301 | SnmpTrapV1PDU.prototype.encode = function (writer) { 302 | var i; 303 | 304 | writer.startSequence(ASN1.Context | ASN1.Constructor | this._op); 305 | this._enterprise.encode(writer); 306 | this._agent_addr.encode(writer); 307 | this._generic_trap.encode(writer); 308 | this._specific_trap.encode(writer); 309 | this._time_stamp.encode(writer); 310 | 311 | writer.startSequence(); 312 | for (i = 0; i < this._varbinds.length; i++) 313 | this._varbinds[i].encode(writer); 314 | writer.endSequence(); 315 | writer.endSequence(); 316 | }; 317 | 318 | function 319 | createPDU(arg) 320 | { 321 | if (typeof (arg) !== 'object') 322 | throw new TypeError('arg (object) is required'); 323 | if (typeof (arg.op) !== 'number') 324 | throw new TypeError('arg.op (number) is reguired'); 325 | 326 | switch (arg.op) { 327 | case OPS.Trap: 328 | return (new SnmpTrapV1PDU(arg)); 329 | default: 330 | return (new SnmpStdPDU(arg)); 331 | } 332 | } 333 | 334 | function 335 | strop(op) 336 | { 337 | var i; 338 | if (typeof (op) != 'number') 339 | throw new TypeError('op (number) is required'); 340 | for (i in OPS) { 341 | if (OPS.hasOwnProperty(i) && OPS[i] == op) 342 | return (i + '(' + op + ')'); 343 | } 344 | return ('(' + op + ')'); 345 | } 346 | 347 | function 348 | strerror(err) 349 | { 350 | var i; 351 | if (typeof (err) != 'number') 352 | throw new TypeError('err (number) is required'); 353 | for (i in ERRORS) { 354 | if (ERRORS.hasOwnProperty(i) && ERRORS[i] == err) 355 | return (i + '(' + err + ')'); 356 | } 357 | return ('(' + err + ')'); 358 | } 359 | 360 | function 361 | strtrap(trap) 362 | { 363 | var i; 364 | if (typeof (trap) !== 'number') 365 | throw new TypeError('trap (number) is required'); 366 | for (i in TRAPS_V1) { 367 | if (TRAPS_V1.hasOwnProperty(i) && TRAPS_V1[i] == trap) 368 | return (i + '(' + trap + ')'); 369 | } 370 | return ('(' + trap + ')'); 371 | } 372 | 373 | module.exports = function _pdu_init() { 374 | var PDU = { 375 | SnmpPDU: SnmpPDU, 376 | createPDU: createPDU, 377 | strop: strop, 378 | strerror: strerror, 379 | strtrap: strtrap 380 | }; 381 | 382 | Object.keys(OPS).forEach(function (o) { 383 | PDU.__defineGetter__(o, function () { return (OPS[o]); }); 384 | }); 385 | 386 | Object.keys(ERRORS).forEach(function (e) { 387 | PDU.__defineGetter__(e, function () { return (ERRORS[e]); }); 388 | }); 389 | 390 | Object.keys(TRAPS_V1).forEach(function (t) { 391 | PDU.__defineGetter__(t, function () { return (TRAPS_V1[t]); }); 392 | }); 393 | 394 | PDU.isSnmpPDU = function (p) { 395 | return ((typeof (p.__snmpjs_magic) === 'string' && 396 | p.__snmpjs_magic === 'SnmpPDU') ? true : false); 397 | }; 398 | 399 | return (PDU); 400 | }(); 401 | -------------------------------------------------------------------------------- /lib/protocol/uint64_t.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | /* 6 | * Rudimentary 64-bit unsigned integer operations. This is needed only for 7 | * one thing: formatting Counter64 values as decimal strings. Since we wrap 8 | * the pair of 32-bit values in this object type, we also use toOctets() to 9 | * encode the value without inspecting its contents. 10 | * 11 | * The division by 10 algorithm is courtesy of the Hacker's Delight 10-17, 12 | * figure 10-10. The rest is trivial and obvious. The operations are named 13 | * in the same way as the amd64 instructions that do the same thing. Two 14 | * things are not supported, at all, because they aren't needed by snmpjs: 15 | * 16 | * - signed numbers 17 | * - division, except by 10 18 | * 19 | * This is all needed only because Javascript provides only 53 bits of 20 | * precision in Numbers, a notorious language defect. 21 | */ 22 | 23 | var fmt = require('util').format; 24 | var TEN = new uint64_t(0, 10); 25 | 26 | function 27 | uint64_t(hi, lo) 28 | { 29 | if (typeof (hi) === 'string' && typeof (lo) === 'undefined') { 30 | var s = hi; 31 | if (hi.substr(0, 2) === '0x') { 32 | hi = hi.slice(2); 33 | 34 | if (hi.length > 16) 35 | throw new RangeError('hex value too large'); 36 | 37 | lo = parseInt(hi.substr(-8, 8), 16); 38 | if (hi.length > 8) 39 | hi = parseInt(hi.substr(0, hi.length - 8), 16); 40 | else 41 | hi = 0; 42 | } else if (hi.substr(0, 1) === '0') { 43 | var i; 44 | hi = 0; 45 | lo = 0; 46 | 47 | for (i = 0; i < (s.length - 1) * 3; i++) { 48 | var p = s.length - 1 - ((i / 3) >>> 0); 49 | var d = parseInt(s.charAt(p), 8); 50 | 51 | if (i > 8) 52 | hi |= !!(d & (1 << (i % 3))) << (i - 8); 53 | else 54 | lo |= !!(d & (1 << (i % 3))) << i; 55 | } 56 | } else { 57 | var a = new uint64_t(0, 0); 58 | 59 | for (i = 0; i < s.length; i++) { 60 | a = mulq(a, 10); 61 | a = addq(a, new uint64_t(0, 62 | parseInt(s.charAt(i), 10))); 63 | } 64 | 65 | this._hi = a._hi; 66 | this._lo = a._lo; 67 | return; 68 | } 69 | } else if (typeof (hi) === 'number' && typeof (lo) === 'undefined') { 70 | lo = hi; 71 | hi = 0; 72 | } 73 | 74 | if (hi % 1 !== 0 || lo % 1 !== 0) 75 | throw new TypeError('hi and lo components must be integers'); 76 | 77 | this._hi = hi >>> 0; 78 | this._lo = lo >>> 0; 79 | } 80 | 81 | function 82 | typecheck(a) 83 | { 84 | if (typeof (a) !== 'object') 85 | a = new uint64_t(a); 86 | 87 | if (typeof (a) !== 'object' || 88 | typeof (a._hi) !== 'number' || typeof (a._lo) !== 'number') 89 | throw new TypeError('argument must be a uint64_t'); 90 | 91 | return (a); 92 | } 93 | 94 | function 95 | addq(a, b) 96 | { 97 | var s0, s16, s32, s48; 98 | var a0, a16, a32, a48; 99 | var b0, b16, b32, b48; 100 | 101 | a = typecheck(a); 102 | b = typecheck(b); 103 | 104 | a0 = (a._lo & 0xffff) >>> 0; 105 | b0 = (b._lo & 0xffff) >>> 0; 106 | s0 = (a0 + b0) >>> 0; 107 | 108 | a16 = (a._lo & 0xffff0000) >>> 16; 109 | b16 = (b._lo & 0xffff0000) >>> 16; 110 | s16 = (a16 + b16 + ((s0 & 0x10000) ? 1 : 0)) >>> 0; 111 | 112 | a32 = (a._hi & 0xffff) >>> 0; 113 | b32 = (b._hi & 0xffff) >>> 0; 114 | s32 = (a32 + b32 + ((s16 & 0x10000) ? 1 : 0)) >>> 0; 115 | 116 | a48 = (a._hi & 0xffff0000) >>> 16; 117 | b48 = (b._hi & 0xffff0000) >>> 16; 118 | s48 = (a48 + b48 + ((s32 & 0x10000) ? 1 : 0)) >>> 0; 119 | 120 | return (new uint64_t(((s48 & 0xffff) << 16) | (s32 & 0xffff), 121 | ((s16 & 0xffff) << 16) | (s0 & 0xffff))); 122 | } 123 | uint64_t.addq = addq; 124 | 125 | function 126 | subq(a, b) 127 | { 128 | var d0, d16, d32, d48; 129 | var a0, a16, a32, a48; 130 | var b0, b16, b32, b48; 131 | 132 | a = typecheck(a); 133 | b = typecheck(b); 134 | 135 | a0 = (a._lo & 0xffff) >>> 0; 136 | b0 = (b._lo & 0xffff) >>> 0; 137 | a16 = (a._lo & 0xffff0000) >>> 16; 138 | b16 = (b._lo & 0xffff0000) >>> 16; 139 | a32 = (a._hi & 0xffff) >>> 0; 140 | b32 = (b._hi & 0xffff) >>> 0; 141 | a48 = (a._hi & 0xffff0000) >>> 16; 142 | b48 = (b._hi & 0xffff0000) >>> 16; 143 | 144 | if (b0 > a0) { 145 | a0 += 0x10000; 146 | if (a16 === 0) { 147 | a16 = 0xffff >>> 0; 148 | if (a32 === 0) { 149 | a32 = 0xffff >>> 0; 150 | --a48; 151 | } else { 152 | --a32; 153 | } 154 | } else { 155 | --a16; 156 | } 157 | } 158 | d0 = (a0 - b0) >>> 0; 159 | 160 | if (b16 > a16) { 161 | a16 += 0x10000; 162 | if (a32 === 0) { 163 | a32 = 0xffff >>> 0; 164 | --a48; 165 | } else { 166 | --a32; 167 | } 168 | } 169 | d16 = (a16 - b16) >>> 0; 170 | 171 | if (b32 > a32) { 172 | a32 += 0x10000; 173 | --a48; 174 | } 175 | d32 = (a32 - b32) >>> 0; 176 | 177 | d48 = (a48 - b48) >>> 0; 178 | 179 | return (new uint64_t(((d48 & 0xffff) << 16) | (d32 & 0xffff), 180 | ((d16 & 0xffff) << 16) | (d0 & 0xffff))); 181 | } 182 | uint64_t.subq = subq; 183 | 184 | function 185 | shlq(a, c) 186 | { 187 | a = typecheck(a); 188 | if (typeof (c) !== 'number' || c % 1 !== 0) 189 | throw new TypeError('count argument must be an integer'); 190 | if (c < 0) 191 | throw new RangeError('count argument must be positive'); 192 | 193 | if (c >= 64) 194 | return (new uint64_t(0, 0)); 195 | 196 | if (c >= 32) 197 | return (new uint64_t((a._lo << (c - 32)) & 0xffffffff, 0)); 198 | 199 | if (c === 0) 200 | return (new uint64_t(a._hi, a._lo)); 201 | 202 | return (new uint64_t(((a._hi << c) & 0xffffffff) | (a._lo >>> (32 - c)), 203 | (a._lo << c) & 0xffffffff)); 204 | } 205 | uint64_t.shlq = shlq; 206 | 207 | function 208 | shrlq(a, c) 209 | { 210 | a = typecheck(a); 211 | if (typeof (c) !== 'number' || c % 1 !== 0) 212 | throw new TypeError('count argument must be an integer'); 213 | if (c < 0) 214 | throw new RangeError('count argument must be positive'); 215 | 216 | if (c >= 64) 217 | return (new uint64_t(0, 0)); 218 | 219 | if (c >= 32) 220 | return (new uint64_t(0, a._hi >>> (c - 32))); 221 | 222 | if (c === 0) 223 | return (new uint64_t(a._hi, a._lo)); 224 | 225 | return (new uint64_t(a._hi >>> c, 226 | (a._lo >>> c) | ((a._hi << (32 - c)) & 0xffffffff))); 227 | } 228 | uint64_t.shrlq = shrlq; 229 | 230 | function 231 | mulq(a, b) 232 | { 233 | var p0, p16, p32, p48; 234 | var a0, a16, a32, a48; 235 | var b0, b16, b32, b48; 236 | var s; 237 | 238 | a = typecheck(a); 239 | b = typecheck(b); 240 | 241 | a0 = (a._lo & 0xffff) >>> 0; 242 | b0 = (b._lo & 0xffff) >>> 0; 243 | a16 = (a._lo & 0xffff0000) >>> 16; 244 | b16 = (b._lo & 0xffff0000) >>> 16; 245 | a32 = (a._hi & 0xffff) >>> 0; 246 | b32 = (b._hi & 0xffff) >>> 0; 247 | a48 = (a._hi & 0xffff0000) >>> 16; 248 | b48 = (b._hi & 0xffff0000) >>> 16; 249 | 250 | p0 = new uint64_t(0, a0 * b0); 251 | p16 = addq(new uint64_t(0, a0 * b16), new uint64_t(0, a16 * b0)); 252 | p32 = addq(new uint64_t(0, a32 * b0), new uint64_t(0, a16 * b16)); 253 | p32 = addq(p32, new uint64_t(0, a0 * b32)); 254 | p48 = addq(new uint64_t(0, a48 * b0), new uint64_t(0, a32 * b16)); 255 | p48 = addq(p48, new uint64_t(0, a16 * b32)); 256 | p48 = addq(p48, new uint64_t(0, a0 * b48)); 257 | 258 | s = addq(p0, shlq(p16, 16)); 259 | s = addq(s, shlq(p32, 32)); 260 | s = addq(s, shlq(p48, 48)); 261 | 262 | return (s); 263 | } 264 | uint64_t.mulq = mulq; 265 | 266 | function 267 | tstq(a) 268 | { 269 | a = typecheck(a); 270 | 271 | return ((a._hi !== 0) || (a._lo !== 0)); 272 | } 273 | uint64_t.tstq = tstq; 274 | 275 | function 276 | cmpq(a, b) 277 | { 278 | a = typecheck(a); 279 | b = typecheck(b); 280 | 281 | if (a._hi > b._hi) 282 | return (1); 283 | if (a._hi < b._hi) 284 | return (-1); 285 | if (a._lo > b._lo) 286 | return (1); 287 | if (a._lo < b._lo) 288 | return (-1); 289 | 290 | return (0); 291 | } 292 | uint64_t.cmpq = cmpq; 293 | 294 | uint64_t.prototype.toString_internal = function uint64_t_toString_internal() { 295 | return (fmt('[%d,%d]', this._hi, this._lo)); 296 | }; 297 | 298 | uint64_t.prototype.toString = function uint64_t_toString() { 299 | var s = ''; 300 | var n = this, q, r; 301 | var v; 302 | 303 | if (!tstq(n)) 304 | return ('0'); 305 | 306 | while (tstq(n)) { 307 | q = addq(shrlq(n, 1), shrlq(n, 2)); 308 | q = addq(q, shrlq(q, 4)); 309 | q = addq(q, shrlq(q, 8)); 310 | q = addq(q, shrlq(q, 16)); 311 | q = addq(q, shrlq(q, 32)); 312 | q = shrlq(q, 3); 313 | 314 | r = subq(n, mulq(q, TEN)); 315 | v = (r._lo + 6) >>> 4; 316 | q = addq(q, new uint64_t(0, v)); 317 | r._lo -= v * 10; 318 | n = q; 319 | 320 | s = r._lo.toString().concat(s); 321 | } 322 | 323 | return (s); 324 | }; 325 | 326 | uint64_t.prototype.toOctets = function uint64_t_toOctets() { 327 | var a = new Array(); 328 | var v = { 329 | hi: this._hi, 330 | lo: this._lo 331 | }; 332 | var len; 333 | 334 | if (v.hi === 0 && v.lo === 0) { 335 | a.push(0); 336 | return (a); 337 | } 338 | 339 | len = 4; 340 | while (v.lo !== 0 || v.hi !== 0 && len > 0) { 341 | a.unshift(v.lo & 0xff); 342 | /* JSSTYLED */ 343 | v.lo >>>= 8; 344 | --len; 345 | } 346 | 347 | while (v.hi !== 0) { 348 | a.unshift(v.hi & 0xff); 349 | /* JSSTYLED */ 350 | v.hi >>>= 8; 351 | } 352 | 353 | return (a); 354 | }; 355 | 356 | module.exports = uint64_t; 357 | -------------------------------------------------------------------------------- /lib/protocol/varbind.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var ASN1 = require('asn1').Ber; 6 | var data = require('./data'); 7 | 8 | function 9 | SnmpVarbind(arg) 10 | { 11 | var self = this; 12 | 13 | if (!arg) 14 | arg = {}; 15 | if (typeof (arg) !== 'object') 16 | throw new TypeError('arg must be an object'); 17 | 18 | this._oid = undefined; 19 | this._data = undefined; 20 | 21 | this.__defineGetter__('oid', function () { 22 | if (self._oid) 23 | return (self._oid.value); 24 | return (undefined); 25 | }); 26 | this.__defineSetter__('oid', function (v) { 27 | if (typeof (v) === 'string') 28 | v = data.createData({ value: v, 29 | type: 'ObjectIdentifier' }); 30 | 31 | if (typeof (v) !== 'object' || !data.isSnmpData(v) || 32 | v.typename != 'ObjectIdentifier') { 33 | throw new TypeError('oid must be a string or ' + 34 | 'SNMP data object of type ObjectIdentifier'); 35 | } 36 | 37 | self._oid = v; 38 | }); 39 | this.__defineGetter__('data', function () { 40 | return (self._data); 41 | }); 42 | this.__defineSetter__('data', function (v) { 43 | if (typeof (v) === 'object' && data.isSnmpData(v)) 44 | self._data = v; 45 | else if (typeof (v) === 'object' && (v instanceof ASN1.Reader)) 46 | self._data = data.createData({ value: v }); 47 | else if (typeof (v) === 'object') 48 | self._data = data.createData(v); 49 | }); 50 | 51 | if (typeof (arg.oid) !== 'undefined') 52 | this.oid = arg.oid; 53 | if (typeof (arg.data) !== 'undefined') 54 | this.data = arg.data; 55 | } 56 | 57 | SnmpVarbind.prototype.__snmpjs_magic = 'SnmpVarbind'; 58 | 59 | SnmpVarbind.prototype.clone = function clone() { 60 | var oclone, dclone; 61 | 62 | if (this._oid) 63 | oclone = this._oid.clone(); 64 | if (this._data) 65 | dclone = this._data.clone(); 66 | 67 | return (new this.constructor({ oid: oclone, data: dclone })); 68 | }; 69 | 70 | SnmpVarbind.prototype.encode = function encode(writer) { 71 | if (typeof (this._oid) === 'undefined') 72 | throw new ReferenceError('Cannot encode undefined oid'); 73 | if (typeof (this._data) == 'undefined') 74 | throw new ReferenceError('Cannot encode undefined data'); 75 | 76 | writer.startSequence(); 77 | this._oid.encode(writer); 78 | this._data.encode(writer); 79 | writer.endSequence(); 80 | }; 81 | 82 | function 83 | createVarbind(arg) 84 | { 85 | return (new SnmpVarbind(arg)); 86 | } 87 | 88 | module.exports = { 89 | SnmpVarbind: SnmpVarbind, 90 | createVarbind: createVarbind, 91 | isSnmpVarbind: function (v) { 92 | return ((typeof (v.__snmpjs_magic) === 'string' && 93 | v.__snmpjs_magic === 'SnmpVarbind') ? true : false); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /lib/provider.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var util = require('util'); 6 | var PDU = require('./protocol/pdu'); 7 | var varbind = require('./protocol/varbind'); 8 | var data = require('./protocol/data'); 9 | var MIB = require('./mib'); 10 | 11 | function 12 | ProviderRequest(op, addr, node, iterate) 13 | { 14 | var self = this; 15 | 16 | this._op = op; 17 | addr = this._addr = data.canonicalizeOID(addr); 18 | this._oid = this._addr.join('.'); 19 | this._done = function () { 20 | throw new Error('BUG in snmpjs! No completion callback set.'); 21 | }; 22 | this._iterate = iterate || 1; 23 | 24 | this.__defineGetter__('op', function () { return (self._op); }); 25 | this.__defineGetter__('addr', function () { return (self._addr); }); 26 | this.__defineGetter__('oid', function () { return (self._oid); }); 27 | this.__defineGetter__('node', function () { return (self._node); }); 28 | this.__defineGetter__('instance', 29 | function () { return (self._instance); }); 30 | this.__defineGetter__('iterate', 31 | function () { return (self._iterate); }); 32 | this.__defineGetter__('done', function () { return (self._done); }); 33 | this.__defineGetter__('value', function () { return (self._value); }); 34 | 35 | this._node = node; 36 | 37 | if (node && node.isAncestor(addr)) 38 | this._instance = addr.slice(node.addr.length, addr.length); 39 | } 40 | 41 | function 42 | _createVarbind(oid, rsd) 43 | { 44 | var vb; 45 | 46 | if (typeof (rsd) === 'number') 47 | vb = rsd; 48 | else if (typeof (rsd) === 'undefined') 49 | vb = undefined; 50 | else if (typeof (rsd) === 'object' && data.isSnmpData(rsd)) 51 | vb = varbind.createVarbind({ oid: oid, data: rsd }); 52 | else 53 | throw new TypeError('Response is of incompatible type'); 54 | 55 | return (vb); 56 | } 57 | 58 | function 59 | readOnlyScalar(prq, rsd) 60 | { 61 | var oid = prq.node.oid + '.0'; 62 | 63 | if (prq.op == PDU.SetRequest) { 64 | prq.done(_createVarbind(PDU.notWritable)); 65 | return; 66 | } 67 | 68 | prq.done(_createVarbind(oid, rsd)); 69 | } 70 | 71 | function 72 | writableScalar(prq, rsd) 73 | { 74 | var oid = prq.node.oid + '.0'; 75 | 76 | prq.done(_createVarbind(oid, rsd)); 77 | } 78 | 79 | module.exports = { 80 | ProviderRequest: ProviderRequest, 81 | readOnlyScalar: readOnlyScalar, 82 | writableScalar: writableScalar 83 | }; 84 | -------------------------------------------------------------------------------- /lib/receiver.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 3 | * Copyright (c) 2013 Joyent, Inc. All rights reserved. 4 | */ 5 | 6 | var dgram = require('dgram'); 7 | var util = require('util'); 8 | var events = require('events'); 9 | var PDU = require('./protocol/pdu'); 10 | var message = require('./protocol/message'); 11 | 12 | function 13 | Receiver(options) 14 | { 15 | if (typeof (options) !== 'object') 16 | throw new TypeError('options (object) is required'); 17 | if (typeof (options.log) !== 'object') 18 | throw new TypeError('options.log (object) is required'); 19 | 20 | this._log = options.log; 21 | this._name = options.name || 'snmpjs'; 22 | 23 | this._malformed_messages = 0; 24 | } 25 | util.inherits(Receiver, events.EventEmitter); 26 | 27 | Receiver.prototype._process_msg = function _process_msg(msg) { 28 | this._log.debug({ 29 | raw: msg.raw, 30 | origin: msg.src, 31 | snmpmsg: msg 32 | }, 'Ignoring PDU of inappropriate type ' + 33 | PDU.strop(msg.pdu.op)); 34 | }; 35 | 36 | Receiver.prototype._augment_msg = function _augment_msg(msg, conn) { 37 | }; 38 | 39 | Receiver.prototype._recv = function _recv(raw, src, conn) { 40 | var msg; 41 | 42 | try { 43 | msg = message.parseMessage({ raw: raw, src: src }); 44 | } catch (err) { 45 | this._malformed_messages++; 46 | this._log.debug({ 47 | err: err, 48 | raw: raw, 49 | origin: src }, 'Invalid SNMP message'); 50 | return; 51 | } 52 | 53 | this._augment_msg(msg, conn); 54 | this._log.trace({ raw: raw, origin: src, snmpmsg: msg }, 55 | 'Received SNMP message'); 56 | this._process_msg(msg); 57 | }; 58 | 59 | Receiver.prototype.createSocket = function createSocket(family) { 60 | var self = this; 61 | var conn; 62 | 63 | if (typeof (family) !== 'string') 64 | throw new TypeError('family (string) is required'); 65 | 66 | conn = dgram.createSocket(family); 67 | conn.on('message', function _recv_binder(msg, rinfo) { 68 | var raw = { 69 | buf: msg, 70 | len: rinfo.size 71 | }; 72 | var src = { 73 | family: family, 74 | address: rinfo.address, 75 | port: rinfo.port 76 | }; 77 | self._recv(raw, src, conn); 78 | }); 79 | 80 | return (conn); 81 | }; 82 | 83 | module.exports = Receiver; 84 | -------------------------------------------------------------------------------- /lib/snmp.jison: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | /* 6 | * The SNMP base message format is almost completely undefined by the relevant 7 | * RFCs (1157, most notably, which uses the verboten "ANY"). We do, however, 8 | * know the following: 9 | * 10 | * 1. A message is a CONSTRUCTED SEQUENCE. If that's not the first thing we 11 | * see, give up hope. 2. The first element in the sequence is an integer that 12 | * identifies the SNMP version. The following values are defined: 13 | * 14 | * 0 - SNMPv1 15 | * 1 - SNMPv2c 16 | * 3 - SNMPv3 17 | * 18 | * After this, it gets spotty. SNMPv1 (RFC 1157) and SNMPv2c (RFC 1901) 19 | * specify that the community string follows, then they diverge: SNMPv1 allows 20 | * anything to follow, while SNMPv2c requires that a PDU follow. The intent in 21 | * v1 appears to have been for authentication data to precede PDU(s), but this 22 | * was never specified in v1 and it seems that a PDU always follows there as 23 | * well. In any case, this is all we will support for now. SNMPv3 (RFC 3412) 24 | * specifies that the version field is followed by another header that 25 | * describes, among other things, which security model is in use, followed by 26 | * security parameters, followed once again by anything (which is 27 | * "e.g., PDUs..."; thanks for the example, jackass, now how about a 28 | * specification?). See also RFC 3416. 29 | * 30 | * What we're actually willing to support: v1 and v2c headers followed by a 31 | * PDU; i.e., 32 | * 33 | * struct message { 34 | * int version_minus_one; 35 | * string community; 36 | * PDU pdu; 37 | * } 38 | * 39 | * The standards provide for a very limited subset of types allowed in varbinds 40 | * within PDUs; namely, the three types specified by simple_syntax, and a 41 | * handful of others (all with the ASN.1 Application bit set) that are 42 | * enumerated explicitly. However, it does not seem at all implausible that 43 | * various MIBs out there may incorporate values of other data types. Therefore 44 | * we can parse any data type in a varbind that (a) has a data handler 45 | * registered for it, and (b) does not conflict with one of the non-Application 46 | * constructed types used by SNMP itself: SEQUENCE, CONTEXT_CONSTRUCTED_[0-9]. 47 | * We do not support any of the latter because the lexer would have to support 48 | * both treating these as zero-length tokens (where we wish to parse the 49 | * contents separately, as in this parser) and as large objects to be bulk 50 | * decoded without interpretation by the parser (where used to instantiate data 51 | * objects within varbinds). For simplicity, this is not permitted and any 52 | * message containing such a varbind will be unparseable. There is only so much 53 | * we are willing to do to accommodate blatant standards violations. 54 | * 55 | * We can parse v3 messages but don't support them; i.e., we don't attempt to 56 | * construct and return protocol objects for them. 57 | * 58 | * Critical conventions to understand: 59 | * 60 | * - yytext is a Buffer. It it not a String, because it must be able to contain 61 | * unprintable characters including NUL. 62 | * 63 | * - The value of any scalar data object is a descendant of SnmpData. It is a 64 | * bug for this parser to interpret the value of any such object. Instead, all 65 | * objects with SNMP/ASN.1 data types are constructed into SnmpData objects and 66 | * passed along as part of the parsed message. 67 | * 68 | * - The value of a list is always an array, and the members (if any) are always 69 | * of the same type as the value of the type the list contains. 70 | */ 71 | 72 | %token 'SEQUENCE' 73 | %token 'INTEGER' 74 | %token 'IP_ADDRESS' 75 | %token 'TIME_TICKS' 76 | %token 'NULL' 77 | %token 'OBJECT_IDENTIFIER' 78 | %token 'OCTET_STRING' 79 | %token 'CONTEXT_CONSTRUCTED_0' 80 | %token 'CONTEXT_CONSTRUCTED_1' 81 | %token 'CONTEXT_CONSTRUCTED_2' 82 | %token 'CONTEXT_CONSTRUCTED_3' 83 | %token 'CONTEXT_CONSTRUCTED_4' 84 | %token 'CONTEXT_CONSTRUCTED_5' 85 | %token 'CONTEXT_CONSTRUCTED_6' 86 | %token 'CONTEXT_CONSTRUCTED_7' 87 | %token 'CONTEXT_CONSTRUCTED_8' 88 | 89 | %start message 90 | 91 | %% 92 | 93 | message 94 | : 'SEQUENCE' integer content {{ 95 | var msg = yy.message.createMessage({ version: $2, 96 | community: $3.community, pdu: $3.pdu }); 97 | yy.setContent(msg); 98 | }} 99 | ; 100 | 101 | content 102 | : string pdu {{ 103 | $$ = { 104 | community: $1, 105 | pdu: $2 106 | }; 107 | }} 108 | | v3_header v3_sec v3_pdu {{ 109 | throw new RangeError('SNMPv3 is not supported yet'); 110 | }} 111 | ; 112 | 113 | v3_header 114 | : 'SEQUENCE' integer integer string integer 115 | ; 116 | 117 | v3_sec 118 | : string 119 | ; 120 | 121 | v3_pdu 122 | : scoped_pdu 123 | | string 124 | ; 125 | 126 | scoped_pdu 127 | : 'SEQUENCE' string string pdu 128 | ; 129 | 130 | pdu 131 | : std_pdu_tag integer integer integer varbind_list {{ 132 | $$ = yy.pdu.createPDU({ op: $1, request_id: $2, 133 | varbinds: $5 }); 134 | $$.error_status = $3; 135 | $$.error_index = $4; 136 | }} 137 | | obsolete_trap_pdu_tag oid ip_address integer integer time_ticks 138 | varbind_list_v1 {{ 139 | $$ = yy.pdu.createPDU({ op: $1, varbinds: $7 }); 140 | $$.enterprise = $2; 141 | $$.agent_addr = $3; 142 | $$.generic_trap = $4; 143 | $$.specific_trap = $5; 144 | $$.time_stamp = $6; 145 | }} 146 | ; 147 | 148 | std_pdu_tag 149 | : 'CONTEXT_CONSTRUCTED_0' {{ $$ = yy.pdu.GetRequest; }} 150 | | 'CONTEXT_CONSTRUCTED_1' {{ $$ = yy.pdu.GetNextRequest; }} 151 | | 'CONTEXT_CONSTRUCTED_2' {{ $$ = yy.pdu.Response; }} 152 | | 'CONTEXT_CONSTRUCTED_3' {{ $$ = yy.pdu.SetRequest; }} 153 | | 'CONTEXT_CONSTRUCTED_5' {{ $$ = yy.pdu.GetBulkRequest; }} 154 | | 'CONTEXT_CONSTRUCTED_6' {{ $$ = yy.pdu.InformRequest; }} 155 | | 'CONTEXT_CONSTRUCTED_7' {{ $$ = yy.pdu.SNMPv2_Trap; }} 156 | | 'CONTEXT_CONSTRUCTED_8' {{ $$ = yy.pdu.Report; }} 157 | ; 158 | 159 | obsolete_trap_pdu_tag 160 | : 'CONTEXT_CONSTRUCTED_4' {{ $$ = yy.pdu.Trap; }} 161 | ; 162 | 163 | varbind_list_v1 164 | : 'SEQUENCE' varbinds {{ 165 | $$ = $2; 166 | }} 167 | | 'SEQUENCE' 168 | | 169 | ; 170 | 171 | varbind_list 172 | : 'SEQUENCE' varbinds {{ 173 | $$ = $2; 174 | }} 175 | | 176 | ; 177 | 178 | varbinds 179 | : varbinds varbind {{ 180 | $$ = $1; 181 | $$.push($2); 182 | }} 183 | | varbind {{ 184 | $$ = [ $1 ]; 185 | }} 186 | ; 187 | 188 | varbind 189 | : 'SEQUENCE' oid value {{ 190 | $$ = yy.varbind.createVarbind({ oid: $2, data: $3 }); 191 | }} 192 | ; 193 | 194 | value 195 | : object_syntax 196 | | null 197 | ; 198 | 199 | object_syntax 200 | : simple_syntax 201 | | application_syntax 202 | ; 203 | 204 | simple_syntax 205 | : integer 206 | | string 207 | | oid 208 | ; 209 | 210 | application_syntax 211 | : ip_address 212 | | time_ticks 213 | | data 214 | ; 215 | 216 | integer 217 | : 'INTEGER' {{ 218 | var reader = new yy.ASN1.Reader(yytext); 219 | $$ = yy.data.createData({ value: reader, type: 'Integer' }); 220 | }} 221 | ; 222 | 223 | string 224 | : 'OCTET_STRING' {{ 225 | var reader = new yy.ASN1.Reader(yytext); 226 | $$ = yy.data.createData({ value: reader, 227 | type: 'OctetString' }); 228 | }} 229 | ; 230 | 231 | oid 232 | : 'OBJECT_IDENTIFIER' {{ 233 | var reader = new yy.ASN1.Reader(yytext); 234 | $$ = yy.data.createData({ value: reader, 235 | type: 'ObjectIdentifier'}); 236 | }} 237 | ; 238 | 239 | ip_address 240 | : 'IP_ADDRESS' {{ 241 | var reader = new yy.ASN1.Reader(yytext); 242 | $$ = yy.data.createData({ value: reader, 243 | type: 'IpAddress' }); 244 | }} 245 | ; 246 | 247 | time_ticks 248 | : 'TIME_TICKS' {{ 249 | var reader = new yy.ASN1.Reader(yytext); 250 | $$ = yy.data.createData({ value: reader, 251 | type: 'TimeTicks' }); 252 | }} 253 | ; 254 | 255 | null 256 | : 'NULL' {{ 257 | var reader = new yy.ASN1.Reader(yytext); 258 | $$ = yy.data.createData({ value: reader, type: 'Null' }); 259 | }} 260 | ; 261 | 262 | data 263 | : 'DATA' {{ 264 | var reader = new yy.ASN1.Reader(yytext); 265 | $$ = yy.data.createData({ value: reader }); 266 | }} 267 | ; 268 | -------------------------------------------------------------------------------- /lib/trap_listener.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var util = require('util'); 6 | var Listener = require('./listener'); 7 | var PDU = require('./protocol/pdu'); 8 | 9 | function 10 | TrapListener(options) 11 | { 12 | Listener.call(this, options); 13 | } 14 | util.inherits(TrapListener, Listener); 15 | 16 | TrapListener.prototype._process_msg = function _process_msg(msg) { 17 | switch (msg.pdu.op) { 18 | case PDU.Trap: 19 | case PDU.InformRequest: 20 | case PDU.SNMPv2_Trap: 21 | this.emit('trap', msg); 22 | break; 23 | case PDU.GetRequest: 24 | case PDU.SetRequest: 25 | case PDU.GetNextRequest: 26 | case PDU.GetBulkRequest: 27 | case PDU.Response: 28 | case PDU.Report: 29 | default: 30 | Listener.prototype._process_msg.call(this, msg); 31 | break; 32 | } 33 | }; 34 | 35 | module.exports = TrapListener; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Keith M Wesolowski ", 3 | "name": "snmpjs", 4 | "description": "Simple Network Management Protocol toolkit", 5 | "version": "0.1.8", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/joyent/node-snmpjs.git" 9 | }, 10 | "bugs": { 11 | "url" : "http://github.com/joyent/node-snmpjs/issues" 12 | }, 13 | "main": "lib/index.js", 14 | "engines": { 15 | "node": ">=0.6.9" 16 | }, 17 | "dependencies": { 18 | "jison": "0.3", 19 | "asn1": "~0.2.2", 20 | "bunyan": "~0.21", 21 | "dtrace-provider": "~0.4" 22 | }, 23 | "devDependencies": { 24 | "tap": "~0.4" 25 | }, 26 | "scripts": { 27 | "install": "jison -o lib/parser.js lib/snmp.jison", 28 | "update": "jison -o lib/parser.js lib/snmp.jison", 29 | "pretest": "which gjslint; if [[ \"$?\" = 0 ]] ; then gjslint --nojsdoc -r lib -r tst; else echo \"Missing gjslint. Skipping lint\"; fi", 30 | "test": "./node_modules/.bin/tap ./test", 31 | "start": "node agent.js" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /smf/manifests/snmpd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /snmpbulkget.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* 3 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 4 | */ 5 | 6 | var snmp = require('./lib/index.js'); 7 | var bunyan = require('bunyan'); 8 | var util = require('util'); 9 | 10 | var client = snmp.createClient({ 11 | log: new bunyan({ name: 'snmpget', level: 'info' }) 12 | }); 13 | 14 | function print_get_response(snmpmsg) 15 | { 16 | snmpmsg.pdu.varbinds.forEach(function (varbind) { 17 | console.log(varbind.oid + ' = ' + varbind.data.value); 18 | }); 19 | } 20 | 21 | var ip = '127.0.0.1'; // process.argv[2]; 22 | var community = 'public'; // process.argv[3]; 23 | var non_repeaters = [ '1.3.6.1.2.1.1' ]; 24 | var repeaters = [ '1.3.6.1.2.1.1.9.1.2', '1.3.6.1.2.1.1.9.1.3' ]; 25 | 26 | client.getBulk(ip, community, non_repeaters, repeaters, 5, function (snmpmsg) { 27 | print_get_response(snmpmsg); 28 | client.unref(); 29 | }); 30 | -------------------------------------------------------------------------------- /snmpget.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* 3 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 4 | */ 5 | 6 | var snmp = require('./lib/index.js'); 7 | var bunyan = require('bunyan'); 8 | var util = require('util'); 9 | 10 | var client = snmp.createClient({ 11 | log: new bunyan({ name: 'snmpget', level: 'info' }) 12 | }); 13 | 14 | function print_get_response(snmpmsg) 15 | { 16 | snmpmsg.pdu.varbinds.forEach(function (varbind) { 17 | console.log(varbind.oid + ' = ' + varbind.data.value); 18 | }); 19 | } 20 | 21 | var ip = process.argv[2]; 22 | var community = process.argv[3]; 23 | var oid = process.argv[4]; 24 | 25 | client.get(ip, community, 0, oid, function (snmpmsg) { 26 | print_get_response(snmpmsg); 27 | client.unref(); 28 | }); 29 | -------------------------------------------------------------------------------- /snmpinform.js: -------------------------------------------------------------------------------- 1 | snmptrap.js -------------------------------------------------------------------------------- /snmpset.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* 3 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 4 | */ 5 | 6 | var snmp = require('./lib/index.js'); 7 | var bunyan = require('bunyan'); 8 | var util = require('util'); 9 | 10 | var client = snmp.createClient({ 11 | log: new bunyan({ name: 'snmpset', level: 'info' }) 12 | }); 13 | 14 | var ip = process.argv[2]; 15 | var community = process.argv[3]; 16 | var oid = process.argv[4]; 17 | var value = process.argv[5]; 18 | 19 | client.set(ip, community, 0, oid, snmp.data.createData({ type: 'Integer', 20 | value: parseInt(value, 10) }), function (snmpmsg) { 21 | // console.log(snmp.pdu.strerror(snmpmsg.pdu.error_status)); 22 | process.exitCode = snmpmsg.pdu.error_status; 23 | client.unref(); 24 | }); 25 | -------------------------------------------------------------------------------- /snmptrap.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* 3 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 4 | */ 5 | 6 | var snmp = require('./lib/index.js'); 7 | var bunyan = require('bunyan'); 8 | var util = require('util'); 9 | var path = require('path'); 10 | 11 | var callback; 12 | if ('snmpinform' == path.basename(process.argv[1]).split('.')[0]) 13 | callback = function (snmpmsg) { 14 | console.log(util.inspect(snmp.message.serializer(snmpmsg), 15 | false, null, true)); 16 | process.exitCode = snmpmsg.pdu.error_status; 17 | client.unref(); 18 | }; 19 | 20 | var client = snmp.createClient({ 21 | log: new bunyan({ 22 | name: typeof (callback) === 'function' ? 'snmpinform' 23 | : 'snmptrap', 24 | level: 'info' 25 | }) 26 | }); 27 | 28 | var ip = process.argv[2]; 29 | var community = process.argv[3]; 30 | // coldStart 31 | var oid = '1.3.6.1.6.3.1.1.5.1'; // process.argv[4]; 32 | // var value = process.argv[5]; 33 | 34 | client.inform(ip, community, 0, oid, [ 35 | snmp.varbind.createVarbind({ 36 | // sysDescr.0 37 | oid: '1.3.6.1.2.1.1.1.0', 38 | data: snmp.data.createData({ 39 | type: 'OctetString', 40 | value: 'TEST' 41 | }) 42 | }) 43 | ], callback); 44 | -------------------------------------------------------------------------------- /snmpwalk.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* 3 | * Copyright (c) 2015 Jan Van Buggenhout. All rights reserved. 4 | */ 5 | 6 | var snmp = require('./lib/index.js'); 7 | var bunyan = require('bunyan'); 8 | var util = require('util'); 9 | 10 | var client = snmp.createClient({ 11 | log: new bunyan({ name: 'snmpwalk', level: 'info' }) 12 | }); 13 | 14 | function print_get_response(snmpmsg) 15 | { 16 | snmpmsg.pdu.varbinds.forEach(function (varbind) { 17 | console.log(varbind.oid + ' = ' + varbind.data.value); 18 | }); 19 | } 20 | 21 | /* jsl:ignore */ 22 | function snmpwalk(ip, community, version, oid, cb, donecb) 23 | { 24 | /* jsl:end */ 25 | function walk(snmpmsg) { 26 | if (snmpmsg.pdu.error_status == snmp.pdu.noSuchName) { 27 | if (donecb) 28 | donecb(); 29 | return; 30 | } 31 | cb(snmpmsg); 32 | client.getNext(ip, community, version, 33 | snmpmsg.pdu.varbinds[0].oid, walk); 34 | } 35 | 36 | client.getNext(ip, community, version, oid, walk); 37 | } 38 | 39 | var ip = process.argv[2]; 40 | var community = process.argv[3]; 41 | var oid = process.argv[4]; 42 | 43 | snmpwalk(ip, community, 0, oid, print_get_response, function () { 44 | client.unref(); 45 | }); 46 | -------------------------------------------------------------------------------- /test/protocol/uint64_t.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Joyent, Inc. All rights reserved. 3 | */ 4 | 5 | var test = require('tap').test; 6 | var uint64_t; 7 | 8 | test('load library', function (t) { 9 | uint64_t = require('../../lib/protocol/uint64_t'); 10 | t.ok(uint64_t, 'module require should work'); 11 | 12 | t.end(); 13 | }); 14 | 15 | test('creation', function (t) { 16 | var a = new uint64_t(0, 0); 17 | t.deepEqual(a, { _hi: 0, _lo: 0 }, 'explicit double zero'); 18 | 19 | a = new uint64_t(0); 20 | t.deepEqual(a, { _hi: 0, _lo: 0 }, 'explicit single zero'); 21 | 22 | a = new uint64_t(42); 23 | t.deepEqual(a, { _hi: 0, _lo: 42 }, 'single small integer'); 24 | 25 | a = new uint64_t('0'); 26 | t.deepEqual(a, { _hi: 0, _lo: 0 }, 'string 0'); 27 | 28 | a = new uint64_t('42'); 29 | t.deepEqual(a, { _hi: 0, _lo: 42 }, 'string small decimal'); 30 | 31 | a = new uint64_t('042'); 32 | t.deepEqual(a, { _hi: 0, _lo: 042 }, 'string small octal'); 33 | 34 | a = new uint64_t('0x42'); 35 | t.deepEqual(a, { _hi: 0, _lo: 0x42 }, 'string small hex'); 36 | 37 | a = new uint64_t('0x1fff00ff'); 38 | t.deepEqual(a, { _hi: 0, _lo: 0x1fff00ff }, 'string medium hex'); 39 | 40 | a = new uint64_t('0x1ffff0000'); 41 | t.deepEqual(a, { _hi: 1, _lo: 0xffff0000 }, 'string 33 hex'); 42 | 43 | a = new uint64_t('0xffff1234ebc068ac'); 44 | t.deepEqual(a, { _hi: 0xffff1234, _lo: 0xebc068ac }, 45 | 'string large hex'); 46 | 47 | a = new uint64_t('18446744073709551615'); 48 | t.deepEqual(a, { _hi: 0xffffffff, _lo: 0xffffffff }, 49 | 'string max decimal'); 50 | 51 | a = new uint64_t(0x12345678, 0x9abcdef0); 52 | t.deepEqual(a, { _hi: 0x12345678, _lo: 0x9abcdef0 }, 53 | 'explicit hex 61 pair'); 54 | 55 | t.throws(function () { 56 | a = new uint64_t('0x123456789abcdef01'); 57 | }, new RangeError('hex value too large')); 58 | 59 | t.throws(function () { 60 | a = new uint64_t({ foo: 'bar' }); 61 | }, new TypeError('hi and lo components must be integers')); 62 | 63 | t.throws(function () { 64 | a = new uint64_t(4.6, 299.000001); 65 | }, new TypeError('hi and lo components must be integers')); 66 | 67 | t.throws(function () { 68 | a = new uint64_t(-2944.12); 69 | }, new TypeError('hi and lo components must be integers')); 70 | 71 | t.end(); 72 | }); 73 | 74 | test('addition', function (t) { 75 | var a = new uint64_t(0, 0x456); 76 | var b = new uint64_t(0, 0x125); 77 | var s = uint64_t.addq(a, b); 78 | t.deepEqual(s, { _hi: 0, _lo: 0x57b }, '456 + 125 = 57b'); 79 | s = uint64_t.addq(b, a); 80 | t.deepEqual(s, { _hi: 0, _lo: 0x57b }, '125 + 456 = 57b'); 81 | 82 | a = new uint64_t(0); 83 | b = new uint64_t(0x147, 0x2c0509); 84 | s = uint64_t.addq(a, b); 85 | t.deepEqual(s, { _hi: 0x147, _lo: 0x2c0509 }, 86 | '0 + 147002c0509 = 147002c0509'); 87 | s = uint64_t.addq(b, a); 88 | t.deepEqual(s, { _hi: 0x147, _lo: 0x2c0509 }, 89 | '147002c0509 + 0 = 147002c0509'); 90 | 91 | a = new uint64_t(0x1bd1, 0x29c00410); 92 | b = new uint64_t(0, 0x29aa05c0); 93 | s = uint64_t.addq(a, b); 94 | t.deepEqual(s, { _hi: 0x1bd1, _lo: 0x536a09d0 }, 95 | '1bd129c00410 + 29aa05c0 = 1bd1536a09d0'); 96 | s = uint64_t.addq(b, a); 97 | t.deepEqual(s, { _hi: 0x1bd1, _lo: 0x536a09d0 }, 98 | '29aa05c0 + 1bd129c00410 = 1bd1536a09d0'); 99 | 100 | a = new uint64_t(0xfeedface, 0xdeadbeef); 101 | b = new uint64_t(0, 0xbeeff00d); 102 | s = uint64_t.addq(a, b); 103 | t.deepEqual(s, { _hi: 0xfeedfacf, _lo: 0x9d9daefc }, 104 | 'feedfacedeadbeef + beeff00d = feedfacf9d9daefc'); 105 | s = uint64_t.addq(b, a); 106 | t.deepEqual(s, { _hi: 0xfeedfacf, _lo: 0x9d9daefc }, 107 | 'beeff00d + feedfacedeadbeef = feedfacf9d9daefc'); 108 | 109 | a = new uint64_t(0xffffffff, 0xffffffff); 110 | b = new uint64_t(0, 1); 111 | s = uint64_t.addq(a, b); 112 | t.deepEqual(s, { _hi: 0, _lo: 0 }, 113 | 'ffffffffffffffff + 1 = 0'); 114 | s = uint64_t.addq(b, a); 115 | t.deepEqual(s, { _hi: 0, _lo: 0 }, 116 | '1 + ffffffffffffffff = 0'); 117 | 118 | t.end(); 119 | }); 120 | 121 | test('subtraction', function (t) { 122 | var a = new uint64_t(0, 0x456); 123 | var b = new uint64_t(0, 0x125); 124 | var d = uint64_t.subq(a, b); 125 | t.deepEqual(d, { _hi: 0, _lo: 0x331 }, '456 - 125 = 331'); 126 | 127 | a = new uint64_t(0xc60a4221, 0xa8c8917b); 128 | b = new uint64_t(0); 129 | d = uint64_t.subq(a, b); 130 | t.deepEqual(d, { _hi: 0xc60a4221, _lo: 0xa8c8917b }, 131 | 'c60a4221a8c8917b - 0 = c60a4221a8c8917b'); 132 | 133 | a = new uint64_t(0x1bd1, 0x4444aaaa); 134 | b = new uint64_t(0x1000, 0x00000000); 135 | d = uint64_t.subq(a, b); 136 | t.deepEqual(d, { _hi: 0xbd1, _lo: 0x4444aaaa }, 137 | '1bd14444aaaa - 100000000000 = bd14444aaaa'); 138 | 139 | a = new uint64_t(0x80000000, 0); 140 | b = new uint64_t(0x7fffffff, 0xffffffff); 141 | d = uint64_t.subq(a, b); 142 | t.deepEqual(d, { _hi: 0, _lo: 1 }, 143 | '8000000000000000 - 7fffffffffffffff = 1'); 144 | 145 | a = new uint64_t(0x4001, 0x00001cc8); 146 | b = new uint64_t(0x2e, 0xfc002bfc); 147 | d = uint64_t.subq(a, b); 148 | t.deepEqual(d, { _hi: 0x3fd2, _lo: 0x3fff0cc }, 149 | '400100001cc8 - 2efc002bfc = 3fd203fff0cc'); 150 | 151 | a = new uint64_t(0); 152 | b = new uint64_t(1); 153 | d = uint64_t.subq(a, b); 154 | t.deepEqual(d, { _hi: 0xffffffff, _lo: 0xffffffff }, 155 | '0 - 1 = ffffffffffffffff'); 156 | 157 | a = new uint64_t(0xa000); 158 | b = new uint64_t(1, 0); 159 | d = uint64_t.subq(a, b); 160 | t.deepEqual(d, { _hi: 0xffffffff, _lo: 0xa000 }, 161 | 'a000 - 100000000 = ffffffff0000a000'); 162 | 163 | a = new uint64_t(0x3cf2, 0xfb002dee); 164 | b = new uint64_t(0x68ad0, 0x016400c2); 165 | d = uint64_t.subq(a, b); 166 | t.deepEqual(d, { _hi: 0xfff9b222, _lo: 0xf99c2d2c }, 167 | '3cf2fb002dee - 68ad0016400c2 = fff9b222f99c2d2c'); 168 | 169 | t.end(); 170 | }); 171 | 172 | test('multiplication', function (t) { 173 | var a = new uint64_t(0, 0x456); 174 | var b = new uint64_t(0, 0x125); 175 | var p = uint64_t.mulq(a, b); 176 | t.deepEqual(p, { _hi: 0, _lo: 0x4f66e }, '456 * 125 = 4f66e'); 177 | 178 | a = new uint64_t(0, 0); 179 | b = new uint64_t(0xffffffff, 0xffffffff); 180 | p = uint64_t.mulq(a, b); 181 | t.deepEqual(p, { _hi: 0, _lo: 0 }, 'product with 0 is 0'); 182 | p = uint64_t.mulq(b, a); 183 | t.deepEqual(p, { _hi: 0, _lo: 0 }, 'product with 0 is 0'); 184 | 185 | a = new uint64_t(0, 1); 186 | b = new uint64_t(0xffffffff, 0); 187 | p = uint64_t.mulq(a, b); 188 | t.deepEqual(p, { _hi: 0xffffffff, _lo: 0 }, 189 | 'ffffffff00000000 * 1 = ffffffff00000000'); 190 | p = uint64_t.mulq(b, a); 191 | t.deepEqual(p, { _hi: 0xffffffff, _lo: 0 }, 192 | '1 * ffffffff00000000 = ffffffff00000000'); 193 | 194 | a = new uint64_t(0, 0x10000000); 195 | p = uint64_t.mulq(a, a); 196 | t.deepEqual(p, { _hi: 0x1000000, _lo: 0 }, 197 | '10000000 * 10000000 = 100000000000000'); 198 | 199 | a = new uint64_t(1, 0); 200 | p = uint64_t.mulq(a, a); 201 | t.deepEqual(p, { _hi: 0, _lo: 0 }, 202 | '100000000 * 100000000 = 0'); 203 | 204 | a = new uint64_t(0, 0x2cc9a); 205 | b = new uint64_t(0x174, 0x5a0236c4); 206 | p = uint64_t.mulq(a, b); 207 | t.deepEqual(p, { _hi: 0x4124bbc, _lo: 0x568121e8 }, 208 | '2cc9a * 1745a0236c4 = 4124bbc568121e8'); 209 | p = uint64_t.mulq(b, a); 210 | t.deepEqual(p, { _hi: 0x4124bbc, _lo: 0x568121e8 }, 211 | '1745a0236c4 * 2cc9a = 4124bbc568121e8'); 212 | 213 | a = new uint64_t(0, 0xffffffff); 214 | p = uint64_t.mulq(a, a); 215 | t.deepEqual(p, { _hi: 0xfffffffe, _lo: 1 }, 216 | 'ffffffff * ffffffff = fffffffe00000001'); 217 | 218 | a = new uint64_t(0, 0xffffffff); 219 | b = new uint64_t(1, 0); 220 | p = uint64_t.mulq(a, b); 221 | t.deepEqual(p, { _hi: 0xffffffff, _lo: 0 }, 222 | 'ffffffff * 100000000 = ffffffff00000000'); 223 | p = uint64_t.mulq(b, a); 224 | t.deepEqual(p, { _hi: 0xffffffff, _lo: 0 }, 225 | '100000000 * ffffffff = ffffffff00000000'); 226 | 227 | a = new uint64_t(0, 0xffffffff); 228 | b = new uint64_t(1, 1); 229 | p = uint64_t.mulq(a, b); 230 | t.deepEqual(p, { _hi: 0xffffffff, _lo: 0xffffffff }, 231 | 'ffffffff * 100000001 = ffffffffffffffff'); 232 | p = uint64_t.mulq(b, a); 233 | t.deepEqual(p, { _hi: 0xffffffff, _lo: 0xffffffff }, 234 | '100000001 * ffffffff = ffffffffffffffff'); 235 | 236 | a = new uint64_t(0, 0xffff); 237 | b = new uint64_t(0, 0x1ffff); 238 | p = uint64_t.mulq(a, b); 239 | t.deepEqual(p, { _hi: 1, _lo: 0xfffd0001 }, 240 | 'ffff * 1ffff = 1fffd0001'); 241 | p = uint64_t.mulq(b, a); 242 | t.deepEqual(p, { _hi: 1, _lo: 0xfffd0001 }, 243 | '1ffff * ffff = 1fffd0001'); 244 | 245 | t.end(); 246 | }); 247 | 248 | test('shift left', function (t) { 249 | var a = new uint64_t(0, 0x456); 250 | var r = uint64_t.shlq(a, 0); 251 | t.deepEqual(r, { _hi: 0, _lo: 0x456 }, '456 << 0 = 456'); 252 | 253 | a = new uint64_t(0, 0); 254 | r = uint64_t.shlq(a, 18); 255 | t.deepEqual(r, { _hi: 0, _lo: 0 }, '0 << 18 = 0'); 256 | r = uint64_t.shlq(a, 46); 257 | t.deepEqual(r, { _hi: 0, _lo: 0 }, '0 << 46 = 0'); 258 | r = uint64_t.shlq(a, 64); 259 | t.deepEqual(r, { _hi: 0, _lo: 0 }, '0 << 64 = 0'); 260 | r = uint64_t.shlq(a, 72); 261 | t.deepEqual(r, { _hi: 0, _lo: 0 }, '0 << 72 = 0'); 262 | 263 | a = new uint64_t(0x5a5a5a5a, 0xa5a5a5a5); 264 | r = uint64_t.shlq(a, 0); 265 | t.deepEqual(r, { _hi: 0x5a5a5a5a, _lo: 0xa5a5a5a5 }, 266 | '5a5a5a5aa5a5a5a5 << 0 = 5a5a5a5aa5a5a5a5'); 267 | r = uint64_t.shlq(a, 1); 268 | t.deepEqual(r, { _hi: 0xb4b4b4b5, _lo: 0x4b4b4b4a }, 269 | '5a5a5a5aa5a5a5a5 << 1 = b4b4b4b54b4b4b4a'); 270 | r = uint64_t.shlq(a, 31); 271 | t.deepEqual(r, { _hi: 0x52d2d2d2, _lo: 0x80000000 }, 272 | '5a5a5a5aa5a5a5a5 << 31 = 52d2d2d280000000'); 273 | r = uint64_t.shlq(a, 32); 274 | t.deepEqual(r, { _hi: 0xa5a5a5a5, _lo: 0 }, 275 | '5a5a5a5aa5a5a5a5 << 32 = a5a5a5a500000000'); 276 | r = uint64_t.shlq(a, 33); 277 | t.deepEqual(r, { _hi: 0x4b4b4b4a, _lo: 0 }, 278 | '5a5a5a5aa5a5a5a5 << 33 = 4b4b4b4a00000000'); 279 | r = uint64_t.shlq(a, 63); 280 | t.deepEqual(r, { _hi: 0x80000000, _lo: 0 }, 281 | '5a5a5a5aa5a5a5a5 << 63 = 8000000000000000'); 282 | r = uint64_t.shlq(a, 64); 283 | t.deepEqual(r, { _hi: 0, _lo: 0 }, 284 | '5a5a5a5aa5a5a5a5 << 64 = 0'); 285 | r = uint64_t.shlq(a, 65); 286 | t.deepEqual(r, { _hi: 0, _lo: 0 }, 287 | '5a5a5a5aa5a5a5a5 << 65 = 0'); 288 | 289 | a = new uint64_t(0, 0xc0010001); 290 | r = uint64_t.shlq(a, 1); 291 | t.deepEqual(r, { _hi: 1, _lo: 0x80020002 }, 292 | 'c0010001 << 1 = 180020002'); 293 | 294 | a = new uint64_t(0xf0000000, 0xf0000000); 295 | r = uint64_t.shlq(a, 1); 296 | t.deepEqual(r, { _hi: 0xe0000001, _lo: 0xe0000000 }, 297 | 'f0000000f0000000 << 1 = e0000001e0000000'); 298 | r = uint64_t.shlq(a, 16); 299 | t.deepEqual(r, { _hi: 0xf000, _lo: 0 }, 300 | 'f0000000f0000000 << 16 = f00000000000'); 301 | 302 | t.end(); 303 | }); 304 | 305 | test('shift right', function (t) { 306 | var a = new uint64_t(0, 0x456); 307 | var r = uint64_t.shrlq(a, 0); 308 | t.deepEqual(r, { _hi: 0, _lo: 0x456 }, '456 >>> 0 = 456'); 309 | 310 | a = new uint64_t(0); 311 | r = uint64_t.shrlq(a, 6); 312 | t.deepEqual(r, { _hi: 0, _lo: 0 }, '0 >>> 6 = 0'); 313 | 314 | a = new uint64_t(0x10000000, 0); 315 | r = uint64_t.shrlq(a, 1); 316 | t.deepEqual(r, { _hi: 0x8000000, _lo: 0 }, 317 | '1000000000000000 >>> 1 = 800000000000000'); 318 | r = uint64_t.shrlq(a, 20); 319 | t.deepEqual(r, { _hi: 0x100, _lo: 0 }, 320 | '1000000000000000 >>> 20 = 10000000000'); 321 | r = uint64_t.shrlq(a, 28); 322 | t.deepEqual(r, { _hi: 1, _lo: 0 }, 323 | '1000000000000000 >>> 28 = 100000000'); 324 | r = uint64_t.shrlq(a, 32); 325 | t.deepEqual(r, { _hi: 0, _lo: 0x10000000 }, 326 | '1000000000000000 >>> 32 = 10000000'); 327 | r = uint64_t.shrlq(a, 33); 328 | t.deepEqual(r, { _hi: 0, _lo: 0x8000000 }, 329 | '1000000000000000 >>> 33 = 8000000'); 330 | 331 | a = new uint64_t(0x4c00, 0x80000000); 332 | r = uint64_t.shrlq(a, 4); 333 | t.deepEqual(r, { _hi: 0x4c0, _lo: 0x8000000 }, 334 | '4c0080000000 >>> 4 = 4c008000000'); 335 | r = uint64_t.shrlq(a, 16); 336 | t.deepEqual(r, { _hi: 0, _lo: 0x4c008000 }, 337 | '4c0080000000 >>> 16 = 4c008000'); 338 | 339 | a = new uint64_t(0xe7802376, 0x5ddefa11); 340 | r = uint64_t.shrlq(a, 30); 341 | t.deepEqual(r, { _hi: 3, _lo: 0x9e008dd9 }, 342 | 'e78023765ddefa11 >>> 30 = 39e008dd9'); 343 | r = uint64_t.shrlq(a, 31); 344 | t.deepEqual(r, { _hi: 1, _lo: 0xcf0046ec }, 345 | 'e78023765ddefa11 >>> 31 = 1cf0046ec'); 346 | r = uint64_t.shrlq(a, 36); 347 | t.deepEqual(r, { _hi: 0, _lo: 0xe780237 }, 348 | 'e78023765ddefa11 >>> 36 = e780237'); 349 | r = uint64_t.shrlq(a, 63); 350 | t.deepEqual(r, { _hi: 0, _lo: 1 }, 351 | 'e78023765ddefa11 >>> 63 = 1'); 352 | r = uint64_t.shrlq(a, 64); 353 | t.deepEqual(r, { _hi: 0, _lo: 0 }, 354 | 'e78023765ddefa11 >>> 64 = 0'); 355 | r = uint64_t.shrlq(a, 65); 356 | t.deepEqual(r, { _hi: 0, _lo: 0 }, 357 | 'e78023765ddefa11 >>> 65 = 0'); 358 | 359 | t.end(); 360 | }); 361 | 362 | test('test for nonzero', function (t) { 363 | var a = new uint64_t(0, 0); 364 | t.notOk(uint64_t.tstq(a), '0 is 0'); 365 | 366 | a = new uint64_t(0, 1); 367 | t.ok(uint64_t.tstq(a), '1 is not 0'); 368 | 369 | a = new uint64_t(1, 0); 370 | t.ok(uint64_t.tstq(a), '100000000 is not 0'); 371 | 372 | a = new uint64_t(1, 1); 373 | t.ok(uint64_t.tstq(a), '100000001 is not 0'); 374 | 375 | a = new uint64_t(0xef002345, 0xffcc4622); 376 | t.ok(uint64_t.tstq(a), 'ef002345ffcc4622 is not 0'); 377 | 378 | t.end(); 379 | }); 380 | 381 | test('comparison', function (t) { 382 | var z = new uint64_t(0, 0); 383 | t.equal(uint64_t.cmpq(z, z), 0, '0 == 0'); 384 | 385 | var a = new uint64_t(1, 0); 386 | t.equal(uint64_t.cmpq(a, z), 1, '100000000 > 0'); 387 | t.equal(uint64_t.cmpq(z, a), -1, '0 < 100000000'); 388 | 389 | var b = new uint64_t(0xfff00000); 390 | t.equal(uint64_t.cmpq(a, b), 1, '100000000 > fff00000'); 391 | t.equal(uint64_t.cmpq(b, a), -1, 'fff00000 < 100000000'); 392 | 393 | var a = new uint64_t(0xffffffff, 0xffffffff); 394 | t.equal(uint64_t.cmpq(a, z), 1, 'ffffffffffffffff > 0'); 395 | t.equal(uint64_t.cmpq(z, a), -1, '0 < ffffffffffffffff'); 396 | t.equal(uint64_t.cmpq(a, b), 1, 'ffffffffffffffff > fff00000'); 397 | t.equal(uint64_t.cmpq(b, a), -1, 'fff00000 < ffffffffffffffff'); 398 | 399 | t.equal(uint64_t.cmpq(42, 878), -1, 'explicit 42 < 878'); 400 | t.equal(uint64_t.cmpq('9000000000', 2000000000), 1, '9e9 > 2e9'); 401 | 402 | t.end(); 403 | }); 404 | 405 | test('toString', function (t) { 406 | var a = new uint64_t(0, 0); 407 | var s = a.toString(); 408 | t.equal(s, '0', 'should be 0'); 409 | 410 | a = new uint64_t(0, 0x456); 411 | s = a.toString(); 412 | t.equal(s, '1110', 'should be 1110'); 413 | 414 | a = new uint64_t(0, 0xffffffff); 415 | s = a.toString(); 416 | t.equal(s, '4294967295', 'should be 4294967295'); 417 | 418 | a = new uint64_t(1, 0); 419 | s = a.toString(); 420 | t.equal(s, '4294967296', 'should be 4294967296'); 421 | 422 | a = new uint64_t(0, 4095); 423 | s = a.toString(); 424 | t.equal(s, '4095', 'should be 4095'); 425 | 426 | a = new uint64_t(0, 6928001); 427 | s = a.toString(); 428 | t.equal(s, '6928001', 'should be 6928001'); 429 | 430 | a = new uint64_t(0x4055a, 0xc80a2941); 431 | s = a.toString(); 432 | t.equal(s, '1131787368147265', 'should be 1131787368147265'); 433 | 434 | a = new uint64_t(0xffffffff, 0xffffffff); 435 | s = a.toString(); 436 | t.equal(s, '18446744073709551615', 'should be 18446744073709551615'); 437 | 438 | t.end(); 439 | }); 440 | 441 | test('toOctets', function (t) { 442 | var a = new uint64_t(0, 0) 443 | var o = a.toOctets(); 444 | t.deepEqual(o, [ 0 ], 'should be [ 0 ]'); 445 | 446 | a = new uint64_t(0x44556677, 0x01020304); 447 | o = a.toOctets(); 448 | t.deepEqual(o, [ 0x44, 0x55, 0x66, 0x77, 0x01, 0x02, 0x03, 0x04 ], 449 | 'should be [ 0x44, 0x55, 0x66, 0x77, 0x01, 0x02, 0x03, 0x04 ]'); 450 | 451 | a = new uint64_t(0, 0x456); 452 | o = a.toOctets(); 453 | t.deepEqual(o, [ 0x04, 0x56 ], 'should be [ 0x04, 0x56 ]'); 454 | 455 | a = new uint64_t(0x456, 0); 456 | o = a.toOctets(); 457 | t.deepEqual(o, [ 0x04, 0x56, 0, 0, 0, 0 ], 458 | 'should be [ 0x04, 0x56, 0, 0, 0, 0 ]'); 459 | 460 | a = new uint64_t(0xfcfcfcfc, 0x80808080); 461 | o = a.toOctets(); 462 | t.deepEqual(o, [ 0xfc, 0xfc, 0xfc, 0xfc, 0x80, 0x80, 0x80, 0x80 ], 463 | 'should be [ 0xfc, 0xfc, 0xfc, 0xfc, 0x80, 0x80, 0x80, 0x80 ]'); 464 | 465 | t.end(); 466 | }); 467 | -------------------------------------------------------------------------------- /tl.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | /* 3 | * Copyright (c) 2013, Joyent, Inc. All rights reserved. 4 | */ 5 | 6 | var snmp = require('./lib/index.js'); 7 | var mib = require('./lib/mib/index.js'); 8 | var bunyan = require('bunyan'); 9 | var fs = require('fs'); 10 | var util = require('util'); 11 | 12 | var config = process.argv[2] || 'tl.json'; 13 | var cfstr = fs.readFileSync(config); 14 | var cf, log_cf; 15 | var log, tl; 16 | 17 | cf = JSON.parse(cfstr); 18 | log_cf = cf.log || { 19 | name: 'snmpd', 20 | level: 'trace' 21 | }; 22 | 23 | log = new bunyan(log_cf); 24 | 25 | tl = snmp.createTrapListener({ 26 | log: log 27 | }); 28 | 29 | tl.on('trap', function (msg) { 30 | console.log(util.inspect(snmp.message.serializer(msg), false, null)); 31 | }); 32 | tl.bind({ family: 'udp4', port: 162 }); 33 | -------------------------------------------------------------------------------- /tl.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "name": "snmpd", 4 | "level": "trace" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tools/jsl.node.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration File for JavaScript Lint 3 | # 4 | # This configuration file can be used to lint a collection of scripts, or to enable 5 | # or disable warnings for scripts that are linted via the command line. 6 | # 7 | 8 | ### Warnings 9 | # Enable or disable warnings based on requirements. 10 | # Use "+WarningName" to display or "-WarningName" to suppress. 11 | # 12 | +ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent 13 | +ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity 14 | +ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement 15 | +anon_no_return_value # anonymous function does not always return value 16 | +assign_to_function_call # assignment to a function call 17 | -block_without_braces # block statement without curly braces 18 | +comma_separated_stmts # multiple statements separated by commas (use semicolons?) 19 | +comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) 20 | +default_not_at_end # the default case is not at the end of the switch statement 21 | +dup_option_explicit # duplicate "option explicit" control comment 22 | +duplicate_case_in_switch # duplicate case in switch statement 23 | +duplicate_formal # duplicate formal argument {name} 24 | +empty_statement # empty statement or extra semicolon 25 | +identifier_hides_another # identifer {name} hides an identifier in a parent scope 26 | -inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement 27 | +incorrect_version # Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version. 28 | +invalid_fallthru # unexpected "fallthru" control comment 29 | +invalid_pass # unexpected "pass" control comment 30 | +jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax 31 | +leading_decimal_point # leading decimal point may indicate a number or an object member 32 | +legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax 33 | +meaningless_block # meaningless block; curly braces have no impact 34 | +mismatch_ctrl_comments # mismatched control comment; "ignore" and "end" control comments must have a one-to-one correspondence 35 | +misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma 36 | +missing_break # missing break statement 37 | +missing_break_for_last_case # missing break statement for last case in switch 38 | +missing_default_case # missing default case in switch statement 39 | +missing_option_explicit # the "option explicit" control comment is missing 40 | +missing_semicolon # missing semicolon 41 | +missing_semicolon_for_lambda # missing semicolon for lambda assignment 42 | +multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs 43 | +nested_comment # nested comment 44 | +no_return_value # function {name} does not always return a value 45 | +octal_number # leading zeros make an octal number 46 | +parseint_missing_radix # parseInt missing radix parameter 47 | +partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag 48 | +redeclared_var # redeclaration of {name} 49 | +trailing_comma_in_array # extra comma is not recommended in array initializers 50 | +trailing_decimal_point # trailing decimal point may indicate a number or an object member 51 | +undeclared_identifier # undeclared identifier: {name} 52 | +unreachable_code # unreachable code 53 | -unreferenced_argument # argument declared but never referenced: {name} 54 | -unreferenced_function # function is declared but never referenced: {name} 55 | +unreferenced_variable # variable is declared but never referenced: {name} 56 | +unsupported_version # JavaScript {version} is not supported 57 | +use_of_label # use of label 58 | +useless_assign # useless assignment 59 | +useless_comparison # useless comparison; comparing identical expressions 60 | -useless_quotes # the quotation marks are unnecessary 61 | +useless_void # use of the void type may be unnecessary (void is always undefined) 62 | +var_hides_arg # variable {name} hides argument 63 | +want_assign_or_call # expected an assignment or function call 64 | +with_statement # with statement hides undeclared variables; use temporary variable instead 65 | 66 | 67 | ### Output format 68 | # Customize the format of the error message. 69 | # __FILE__ indicates current file path 70 | # __FILENAME__ indicates current file name 71 | # __LINE__ indicates current line 72 | # __COL__ indicates current column 73 | # __ERROR__ indicates error message (__ERROR_PREFIX__: __ERROR_MSG__) 74 | # __ERROR_NAME__ indicates error name (used in configuration file) 75 | # __ERROR_PREFIX__ indicates error prefix 76 | # __ERROR_MSG__ indicates error message 77 | # 78 | # For machine-friendly output, the output format can be prefixed with 79 | # "encode:". If specified, all items will be encoded with C-slashes. 80 | # 81 | # Visual Studio syntax (default): 82 | +output-format __FILE__(__LINE__): __ERROR__ 83 | # Alternative syntax: 84 | #+output-format __FILE__:__LINE__: __ERROR__ 85 | 86 | 87 | ### Context 88 | # Show the in-line position of the error. 89 | # Use "+context" to display or "-context" to suppress. 90 | # 91 | +context 92 | 93 | 94 | ### Control Comments 95 | # Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for 96 | # the /*@keyword@*/ control comments and JScript conditional comments. (The latter is 97 | # enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, 98 | # although legacy control comments are enabled by default for backward compatibility. 99 | # 100 | -legacy_control_comments 101 | 102 | 103 | ### Defining identifiers 104 | # By default, "option explicit" is enabled on a per-file basis. 105 | # To enable this for all files, use "+always_use_option_explicit" 106 | -always_use_option_explicit 107 | 108 | # Define certain identifiers of which the lint is not aware. 109 | # (Use this in conjunction with the "undeclared identifier" warning.) 110 | # 111 | # Common uses for webpages might be: 112 | +define __dirname 113 | +define clearInterval 114 | +define clearTimeout 115 | +define console 116 | +define exports 117 | +define global 118 | +define module 119 | +define process 120 | +define require 121 | +define setInterval 122 | +define setTimeout 123 | +define Buffer 124 | +define JSON 125 | +define Math 126 | 127 | ### JavaScript Version 128 | # To change the default JavaScript version: 129 | #+default-type text/javascript;version=1.5 130 | #+default-type text/javascript;e4x=1 131 | 132 | ### Files 133 | # Specify which files to lint 134 | # Use "+recurse" to enable recursion (disabled by default). 135 | # To add a set of files, use "+process FileName", "+process Folder\Path\*.js", 136 | # or "+process Folder\Path\*.htm". 137 | # 138 | 139 | --------------------------------------------------------------------------------