snimpy-0.8.13/0000755000076400001440000000000013360401663013562 5ustar bernatusers00000000000000snimpy-0.8.13/snimpy.egg-info/0000755000076400001440000000000013360401663016573 5ustar bernatusers00000000000000snimpy-0.8.13/snimpy.egg-info/requires.txt0000644000076400001440000000004113360401662021165 0ustar bernatusers00000000000000cffi>=1.0.0 pysnmp>=4 setuptools snimpy-0.8.13/snimpy.egg-info/dependency_links.txt0000644000076400001440000000000113360401662022640 0ustar bernatusers00000000000000 snimpy-0.8.13/snimpy.egg-info/SOURCES.txt0000644000076400001440000000275013360401663020463 0ustar bernatusers00000000000000.gitignore .gitmodules .travis.yml AUTHORS.rst CONTRIBUTING.rst HISTORY.rst MANIFEST.in Makefile README.rst requirements.txt setup.py tox.ini version.txt docs/Makefile docs/api.rst docs/conf.py docs/contributing.rst docs/history.rst docs/index.rst docs/installation.rst docs/license.rst docs/usage.rst docs/_static/snimpy.svg docs/_themes/.gitignore docs/_themes/LICENSE docs/_themes/README docs/_themes/flask_theme_support.py docs/_themes/flask/layout.html docs/_themes/flask/relations.html docs/_themes/flask/theme.conf docs/_themes/flask/static/flasky.css_t docs/_themes/flask_small/layout.html docs/_themes/flask_small/theme.conf docs/_themes/flask_small/static/flasky.css_t examples/add-vlan.py examples/disable-port-ingress-filtering.py examples/enable-lldp.py examples/get-serial.py examples/list-interfaces.py examples/list-routes.py examples/rename-vlan.py examples/set-syslog-ntp.py examples/vlan-and-interfaces.py man/snimpy.1 snimpy/__init__.py snimpy/__main__.py snimpy/_version.py snimpy/basictypes.py snimpy/config.py snimpy/main.py snimpy/manager.py snimpy/mib.py snimpy/smi_build.py snimpy/snmp.py snimpy.egg-info/PKG-INFO snimpy.egg-info/SOURCES.txt snimpy.egg-info/dependency_links.txt snimpy.egg-info/entry_points.txt snimpy.egg-info/not-zip-safe snimpy.egg-info/requires.txt snimpy.egg-info/top_level.txt tests/SNIMPY-INVALID-MIB.mib tests/SNIMPY-MIB.mib tests/agent.py tests/test_basictypes.py tests/test_conf.py tests/test_main.py tests/test_manager.py tests/test_mib.py tests/test_snmp.pysnimpy-0.8.13/snimpy.egg-info/top_level.txt0000644000076400001440000000000713360401662021321 0ustar bernatusers00000000000000snimpy snimpy-0.8.13/snimpy.egg-info/entry_points.txt0000644000076400001440000000006113360401662022065 0ustar bernatusers00000000000000[console_scripts] snimpy = snimpy.main:interact snimpy-0.8.13/snimpy.egg-info/PKG-INFO0000644000076400001440000001725713360401662017703 0ustar bernatusers00000000000000Metadata-Version: 1.1 Name: snimpy Version: 0.8.13 Summary: interactive SNMP tool Home-page: https://github.com/vincentbernat/snimpy Author: Vincent Bernat Author-email: bernat@luffy.cx License: UNKNOWN Description: =============================== snimpy =============================== .. image:: https://badge.fury.io/py/snimpy.png :target: http://badge.fury.io/py/snimpy .. image:: https://travis-ci.org/vincentbernat/snimpy.png?branch=master :target: https://travis-ci.org/vincentbernat/snimpy .. image:: https://coveralls.io/repos/vincentbernat/snimpy/badge.png :target: https://coveralls.io/r/vincentbernat/snimpy Interactive SNMP tool. *Snimpy* is a Python-based tool providing a simple interface to build SNMP query. Here is a very simplistic example that allows us to display the routing table of a given host:: load("IP-FORWARD-MIB") m=M("localhost", "public", 2) routes = m.ipCidrRouteNextHop for x in routes: net, netmask, tos, src = x print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src)) You can either use *Snimpy* interactively throught its console (derived from Python own console or from IPython_ if available) or write *Snimpy* scripts which are just Python scripts with some global variables available. .. _IPython: http://ipython.org * Free software: ISC license * Documentation: http://snimpy.rtfd.org. *Snimpy* requires libsmi_ to work correctly. See the documentation for more information. .. _libsmi: https://www.ibr.cs.tu-bs.de/projects/libsmi/ Features -------- *Snimpy* is aimed at being the more Pythonic possible. You should forget that you are doing SNMP requests. *Snimpy* will rely on MIB to hide SNMP details. Here are some "features": * MIB parser based on libsmi (through CFFI) * SNMP requests are handled by PySNMP (SNMPv1, SNMPv2 and SNMPv3 support) * scalars are just attributes of your session object * columns are like a Python dictionary and made available as an attribute * getting an attribute is like issuing a GET method * setting an attribute is like issuing a SET method * iterating over a table is like using GETNEXT * when something goes wrong, you get an exception History ------- 0.8.13 (2018-10-12) +++++++++++++++++++ * Compatibility with Python 3.7. * Fix an issue with implied index when reusing indexes between tables. 0.8.12 (2017-10-02) +++++++++++++++++++ * Support for more recent versions of IPython. * Support for SNMPv3 context name. * Support for notification nodes (MIB only). 0.8.11 (2016-08-13) +++++++++++++++++++ * Fix IPython interactive shell. * Fix IPv6 handling for sessions. * Ability for a session to return None instead of raising an exception. 0.8.10 (2016-02-16) +++++++++++++++++++ * Ability to walk a table (if the first index is accessible). * Ability to do a partial walk (courtesy of Alex Unigovsky). 0.8.8 (2015-11-15) ++++++++++++++++++ * Fix thread-safety problem introduced in 0.8.6. This also undo any improvement advertised in 0.8.6 when using multiple threads. However, performance should be kept when using a single thread. 0.8.7 (2015-11-14) ++++++++++++++++++ * Ability to specify a module name when querying a manager. * Compatibility with PySNMP 4.3 * Array-like interface for OIDs. * Ability to restrict lookups to a specific MIB: m['IF-MIB'].ifDescr. * Fix multithread support with SNMPv3 (with a performance impact). 0.8.6 (2015-06-24) ++++++++++++++++++ * Major speed improvement. * Major memory usage improvement. 0.8.5 (2015-04-04) ++++++++++++++++++ * Ability to set SMI search path (with ``mib.path()``) * Fix documentation build on *Read the Doc*. * Add a loose mode to manager to loosen type coercion. 0.8.4 (2015-02-10) ++++++++++++++++++ * More CFFI workarounds, including cross-compilation support. * Ability to override a node type. * Automatic workaround for "SNMP too big" error message. 0.8.3 (2014-08-18) ++++++++++++++++++ * IPv6 support. 0.8.2 (2014-06-08) ++++++++++++++++++ * Minor bugfixes. 0.8.1 (2013-10-25) ++++++++++++++++++ * Workaround a problem with CFFI extension installation. 0.8.0 (2013-10-19) ++++++++++++++++++++ * Python 3.3 support. Pypy support. * PEP8 compliant. * Sphinx documentation. * Octet strings with a display hint are now treated differently than plain octet strings (unicode). Notably, they can now be set using the displayed format (for example, for MAC addresses). 0.7.0 (2013-09-23) ++++++++++++++++++ * Major rewrite. * SNMP support is now provided through PySNMP_. * MIB parsing is still done with libsmi_ but through CFFI instead of a C module. * More unittests. Many bugfixes. .. _PySNMP: http://pysnmp.sourceforge.net/ .. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/ 0.6.4 (2013-03-21) ++++++++++++++++++ * GETBULK support. * MacAddress SMI type support. 0.6.3 (2012-04-13) ++++++++++++++++++ * Support for IPython 0.12. * Minor bugfixes. 0.6.2 (2012-01-19) ++++++++++++++++++ * Ability to return None instead of getting an exception. 0.6.1 (2012-01-14) ++++++++++++++++++ * Thread safety and efficiency. 0.6 (2012-01-10) ++++++++++++++++++ * SNMPv3 support 0.5.1 (2011-08-07) ++++++++++++++++++ * Compatibility with IPython 0.11. * Custom timeouts and retries. 0.5 (2010-02-03) ++++++++++++++++++ * Check conformity of loaded modules. * Many bugfixes. 0.4 (2009-06-06) ++++++++++++++++++ * Allow to cache requests. 0.3 (2008-11-23) ++++++++++++++++++ * Provide a manual page. * Use a context manager to group SET requests. 0.2.1 (2008-09-28) ++++++++++++++++++ * First release on PyPI. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: ISC License (ISCL) Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: System :: Networking Classifier: Topic :: Utilities Classifier: Topic :: System :: Monitoring snimpy-0.8.13/snimpy.egg-info/not-zip-safe0000644000076400001440000000000112621664265021031 0ustar bernatusers00000000000000 snimpy-0.8.13/.gitmodules0000644000076400001440000000015413012531051015724 0ustar bernatusers00000000000000[submodule "docs/_themes"] path = docs/_themes url = https://github.com/mitsuhiko/flask-sphinx-themes.git snimpy-0.8.13/Makefile0000644000076400001440000000255613357726113015240 0ustar bernatusers00000000000000.PHONY: clean-pyc clean-build docs open := $(shell { which xdg-open || which open; } 2>/dev/null) help: @echo "Please use \`make ' where is one of" @echo " clean-build to remove build artifacts" @echo " clean-pyc to remove Python file artifacts" @echo " lint to check style with flake8" @echo " test to run tests quickly with the default Python" @echo " testall to run tests on every Python version with tox" @echo " coverage to check code coverage quickly with the default Python" @echo " docs to generate Sphinx HTML documentation, including API docs" @echo " release to package and upload a release" @echo " sdist to package" clean: clean-build clean-pyc clean-build: rm -fr build/ rm -fr dist/ rm -fr *.egg-info clean-pyc: find . -name '*.pyc' -type f -exec rm -f {} + find . -name '*.pyo' -type f -exec rm -f {} + find . -name '*~' -type f -exec rm -f {} + find . -name '__pycache__' -type d -exec rm -rf {} + lint: flake8 snimpy tests test: python -m nose test-all: tox coverage: coverage run --source snimpy setup.py test coverage report -m coverage html $(open) htmlcov/index.html docs: $(MAKE) -C docs clean $(MAKE) -C docs html $(open) docs/_build/html/index.html release: clean python setup.py sdist upload sdist: clean python setup.py sdist ls -l dist snimpy-0.8.13/docs/0000755000076400001440000000000013360401663014512 5ustar bernatusers00000000000000snimpy-0.8.13/docs/contributing.rst0000644000076400001440000000004013012531051017732 0ustar bernatusers00000000000000.. include:: ../CONTRIBUTING.rstsnimpy-0.8.13/docs/Makefile0000644000076400001440000001517113012531051016144 0ustar bernatusers00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."snimpy-0.8.13/docs/usage.rst0000644000076400001440000001523213012531051016340 0ustar bernatusers00000000000000======== Usage ======== Invocation ---------- There are three ways to use *Snimpy*: 1. Interactively through a console. 2. As a script interpreter. 3. As a regular Python module. Interactive use +++++++++++++++ *Snimpy* can be invoked with either `snimpy` or `python -m snimpy`. Without any other arhument, the interactive console is spawned. Otherwise, the given script is executed and the remaining arguments are served as arguments for the script. When running interactively, you get a classic Python environment. There are two additional objects available: * The `load()` method that takes a MIB name or a path to a filename. The MIB will be loaded into memory and made available in all SNMP managers:: load("IF-MIB") load("/usr/share/mibs/ietf/IF-MIB") * The `M` class which is used to instantiate a manager (a SNMP client):: m = M() m = M(host="localhost", community="private", version=2) m = M("localhost", "private", 2) m = M(community="private") m = M(version=3, secname="readonly", authprotocol="MD5", authpassword="authpass", privprotocol="AES", privpassword="privpass") A manager instance contains all the scalars and the columns in MIB loaded with the `load()` method. There is no table, node or other entities. For a scalar, getting and setting a value is a simple as:: print(m.sysDescr) m.sysName = "newhostname" For a column, you get a dictionary-like interface:: for index in m.ifTable: print(repr(m.ifDescr[index])) m.ifAdminStatus[3] = "down" If you care about efficiency, note that the above snippet will walk the table twice: once to retrieve the index to loop over and once to retrieve the values. This could be avoided with:: for index, value in m.ifDescr.iteritems(): print(repr(value)) Furthermore, you can pass partial index values to `iteritems()` to limit walked table rows to a specific subset:: for index, value in m.ipNetToMediaPhysAddress.iteritems(10): print(repr(value)) If you don't need values you can use subscript syntax for this as well:: for index in m.ipNetToMediaPhysAddress[10]: print(repr(index)) Another way to avoid those extra SNMP requests is to enable the caching mechanism which is disabled by default:: import time m = M("localhost", cache=True) print(m.sysUpTime) time.sleep(1) print(m.sysUpTime) time.sleep(1) print(m.sysUpTime) time.sleep(10) print(m.sysUpTime) You can also specify the number of seconds data should be cached:: m = M("localhost", cache=20) Also note that iterating over a table require an accessible index. Old MIB usually have accessible indexes. If this is not the case, you'll have to iterate on a column instead. For example, the first example could be written as:: for index in m.ifDescr: print(repr(m.ifDescr[index])) If you want to group several write into a single request, you can do it with `with` keyword:: with M("localhost", "private") as m: m.sysName = "toto" m.ifAdminStatus[20] = "down" It's also possible to set a custom timeout and a custom value for the number of retries. For example, to wait 2.5 seconds before timeout occurs and retry 10 times, you can use:: m = M("localhost", timeout=2.5, retries=10) *Snimpy* will stop on any error with an exception. This allows you to not check the result at each step. Your script can't go awry. If this behaviour does not suit you, it is possible to suppress exceptions when querying inexistant objects. Instead of an exception, you'll get `None`:: m = M("localhost", none=True) If for some reason, you need to specify the module you want to use to lookup a node, you can do that using the following syntax:: print(m['SNMPv2-MIB'].sysDescr) print(m['IF-MIB'].ifNumber) Script interpreter ++++++++++++++++++ *Snimpy* can be run as a script interpreter. There are two ways to do this. The first one is to invoke *Snimpy* and provide a script name as well as any argument you want to pass to the script:: $ snimpy example-script.py arg1 arg2 $ python -m snimpy example-script.py arg1 arg2 The second one is to use *Snimpy* as a shebang_ interpreter. For example, here is a simple script:: #!/usr/bin/env snimpy load("IF-MIB") m = M("localhost") print(m.ifDescr[0]) The script can be invoked as any shell script. .. _shebang: http://en.wikipedia.org/wiki/Shebang_(Unix) Inside the script, you can use any valid Python code. You also get the `load()` method and the `M` class available, like for the interactive use. Regular Python module +++++++++++++++++++++ *Snimpy* can also be imported as a regular Python module:: from snimpy.manager import Manager as M from snimpy.manager import load load("IF-MIB") m = M("localhost") print(m.ifDescr[0]) About "major SMI errors" ------------------------ If you get an exception like `RAPID-CITY contains major SMI errors (check with smilint -s -l1)`, this means that there are some grave errors in this MIB which may lead to segfaults if the MIB is used as is. Usually, this means that some identifier are unknown. Use `smilint -s -l1 YOUR-MIB` to see what the problem is and try to solve all problems reported by lines beginning by `[1]`. For example:: $ smilint -s -l1 rapid_city.mib rapid_city.mib:30: [1] failed to locate MIB module `IGMP-MIB' rapid_city.mib:32: [1] failed to locate MIB module `DVMRP-MIB' rapid_city.mib:34: [1] failed to locate MIB module `IGMP-MIB' rapid_city.mib:27842: [1] unknown object identifier label `igmpInterfaceIfIndex' rapid_city.mib:27843: [1] unknown object identifier label `igmpInterfaceQuerier' rapid_city.mib:27876: [1] unknown object identifier label `dvmrpInterfaceIfIndex' rapid_city.mib:27877: [1] unknown object identifier label `dvmrpInterfaceOperState' rapid_city.mib:27894: [1] unknown object identifier label `dvmrpNeighborIfIndex' rapid_city.mib:27895: [1] unknown object identifier label `dvmrpNeighborAddress' rapid_city.mib:32858: [1] unknown object identifier label `igmpCacheAddress' rapid_city.mib:32858: [1] unknown object identifier label `igmpCacheIfIndex' To solve the problem here, load `IGMP-MIB` and `DVMRP-MIB` before loading `rapid_city.mib`. `IGMP-MIB` should be pretty easy to find. For `DVMRP-MIB`, try Google. Download it and use `smistrip` to get the MIB. You can check that the problem is solved with this command:: $ smilint -p ../cisco/IGMP-MIB.my -p ./DVMRP-MIB -s -l1 rapid_city.mib You will get a lot of errors in `IGMP-MIB` and `DVMRP-MIB` but no line with `[1]`: everything should be fine. To load `rapid_city.mib`, you need to do this:: load("../cisco/IGMP-MIB.my") load("./DVMRP-MIB") load("rapid_city.mib") snimpy-0.8.13/docs/index.rst0000644000076400001440000000451313012531051016343 0ustar bernatusers00000000000000Snimpy: interactive SNMP tool ==================================================== *Snimpy* is a Python-based tool providing a simple interface to build SNMP query. Here is a very simplistic example that allows us to display the routing table of a given host:: load("IP-FORWARD-MIB") m=M("localhost", "public", 2) routes = m.ipCidrRouteNextHop for x in routes: net, netmask, tos, src = x print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src)) You can either use *Snimpy* interactively throught its console (derived from Python own console or from IPython_ if available) or write *Snimpy* scripts which are just Python scripts with some global variables available. .. _IPython: http://ipython.org Why another tool? ----------------- There are a lot of SNMP tools available but most of them have important drawback when you need to reliably automatize operations. `snmpget`, `snmpset` and `snmpwalk` are difficult to use in scripts. Errors are printed on standard output and there is no easy way to tell if the command was successful or not. Moreover, results can be multiline (a long HexString for example). At least, automatisation is done through the shell and OID or bit manipulation are quite difficult. Net-SNMP provides officiel bindings for Perl and Python. Unfortunately, the integration is quite poor. You don't have an easy way to load and browse MIBs and error handling is inexistant. For example, the Python bindings will return None for a non-existant OID. Having to check for this on each request is quite cumbersome. For Python, there are other bindings. For example, pysnmp_ provides a pure Python implementation. However, MIBs need to be compiled. Moreover, the exposed interface is still low-level. Sending a simple SNMP GET can either take 10 lines or one line wrapped into 10 lines. .. _pysnmp: http://pysnmp.sourceforge.net/ The two main points of *Snimpy* are: * very high-level interface relying on MIBs * raise exceptions when something goes wrong Meantime, another Pythonic SNMP library based on Net-SNMP has been released: `Easy SNMP`_. Its interface is a less Pythonic than *Snimpy* but it doesn't need MIBs to work. .. _Easy SNMP: https://github.com/fgimian/easysnmp Contents --------- .. toctree:: :maxdepth: 1 installation usage api contributing license history snimpy-0.8.13/docs/license.rst0000644000076400001440000000174513012531051016662 0ustar bernatusers00000000000000======== License ======== *Snimpy* is licensed under the ISC license. It basically means: do whatever you want with it as long as the copyright sticks around, the conditions are not modified and the disclaimer is present. .. include:: ../AUTHORS.rst ISC License ----------- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. snimpy-0.8.13/docs/_themes/0000755000076400001440000000000013360401663016136 5ustar bernatusers00000000000000snimpy-0.8.13/docs/_themes/flask_theme_support.py0000644000076400001440000001141312272767420022575 0ustar bernatusers00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Generic, Whitespace, Punctuation, Other, Literal class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: #Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } snimpy-0.8.13/docs/_themes/flask/0000755000076400001440000000000013360401663017236 5ustar bernatusers00000000000000snimpy-0.8.13/docs/_themes/flask/static/0000755000076400001440000000000013360401663020525 5ustar bernatusers00000000000000snimpy-0.8.13/docs/_themes/flask/static/flasky.css_t0000644000076400001440000002154612272767474023103 0ustar bernatusers00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0 0 20px 0; margin: 0; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; } dl pre, blockquote pre, li pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } @media screen and (max-width: 870px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } @media screen and (max-width: 875px) { body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; } } /* scrollbars */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { display: block; height: 10px; } ::-webkit-scrollbar-button:vertical:increment { background-color: #fff; } ::-webkit-scrollbar-track-piece { background-color: #eee; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:vertical { height: 50px; background-color: #ccc; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:horizontal { width: 50px; background-color: #ccc; -webkit-border-radius: 3px; } /* misc. */ .revsys-inline { display: none!important; }snimpy-0.8.13/docs/_themes/flask/relations.html0000644000076400001440000000111612272767472022141 0ustar bernatusers00000000000000

Related Topics

snimpy-0.8.13/docs/_themes/flask/theme.conf0000644000076400001440000000024412272767472021225 0ustar bernatusers00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px touch_icon = snimpy-0.8.13/docs/_themes/flask/layout.html0000644000076400001440000000126512272767472021463 0ustar bernatusers00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %}{% endblock %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {%- block footer %} {% if pagename == 'index' %}
{% endif %} {%- endblock %} snimpy-0.8.13/docs/_themes/LICENSE0000644000076400001440000000337512272767420017162 0ustar bernatusers00000000000000Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. snimpy-0.8.13/docs/_themes/flask_small/0000755000076400001440000000000013360401663020426 5ustar bernatusers00000000000000snimpy-0.8.13/docs/_themes/flask_small/static/0000755000076400001440000000000013360401663021715 5ustar bernatusers00000000000000snimpy-0.8.13/docs/_themes/flask_small/static/flasky.css_t0000644000076400001440000001100112272767474024254 0ustar bernatusers00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } snimpy-0.8.13/docs/_themes/flask_small/theme.conf0000644000076400001440000000027012272767472022414 0ustar bernatusers00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px github_fork = '' snimpy-0.8.13/docs/_themes/flask_small/layout.html0000644000076400001440000000125312272767472022650 0ustar bernatusers00000000000000{% extends "basic/layout.html" %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
{% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} snimpy-0.8.13/docs/_themes/README0000644000076400001440000000210512272767420017023 0ustar bernatusers00000000000000Flask Sphinx Styles =================== This repository contains sphinx styles for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: - 'flask' - the standard flask documentation theme for large projects - 'flask_small' - small one-page theme. Intended to be used by very small addon libraries for flask. The following options exist for the flask_small theme: [options] index_logo = '' filename of a picture in _static to be used as replacement for the h1 in the index.rst file. index_logo_height = 120px height of the index logo github_fork = '' repository name on github for the "fork me" badge snimpy-0.8.13/docs/_themes/.gitignore0000644000076400001440000000002612272767420020133 0ustar bernatusers00000000000000*.pyc *.pyo .DS_Store snimpy-0.8.13/docs/conf.py0000644000076400001440000000243013012531051015775 0ustar bernatusers00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import os rtd = os.environ.get('READTHEDOCS', None) == 'True' cwd = os.getcwd() project_root = os.path.dirname(cwd) sys.path.insert(0, project_root) # -- Don't try to load CFFI (doesn't work on RTD) ----------------------------- if rtd: from mock import Mock sys.modules['cffi'] = Mock() sys.modules['cffi.verifier'] = Mock() import snimpy # -- General configuration ---------------------------------------------------- extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' # General information about the project. project = u'Snimpy' copyright = u'2015, Vincent Bernat' version = snimpy.__version__ release = snimpy.__version__ exclude_patterns = ['_build'] pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' html_static_path = ['_static'] html_use_modindex = False html_theme_options = { "index_logo": "snimpy.svg", "index_logo_height": "200px" } htmlhelp_basename = 'snimpydoc' snimpy-0.8.13/docs/history.rst0000644000076400001440000000003313012531051016726 0ustar bernatusers00000000000000.. include:: ../HISTORY.rstsnimpy-0.8.13/docs/api.rst0000644000076400001440000000143513012531051016005 0ustar bernatusers00000000000000============== API reference ============== While *Snimpy* is targeted at being used interactively or through simple scripts, you can also use it from your Python program. It provides a high-level interface as well as lower-level ones. However, the effort is only put in th :mod:`manager` module and other modules are considered as internal details. :mod:`manager` module ---------------------- .. automodule:: snimpy.manager :members: Manager, load Internal modules ---------------- Those modules shouldn't be used directly. :mod:`mib` module ~~~~~~~~~~~~~~~~~ .. automodule:: snimpy.mib :members: :mod:`snmp` module ~~~~~~~~~~~~~~~~~~ .. automodule:: snimpy.snmp :members: :mod:`basictypes` module ~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: snimpy.basictypes :members: snimpy-0.8.13/docs/installation.rst0000644000076400001440000000202313012531051017727 0ustar bernatusers00000000000000============ Installation ============ At the command line:: $ easy_install snimpy Or, if you have virtualenvwrapper installed:: $ mkvirtualenv snimpy $ pip install snimpy *Snimpy* requires libsmi_, a library to access SMI MIB information. You need to install both the library and the development headers. If *Snimpy* complains to not find ``smi.h``, you can help by specifying where this file is located by exporting the appropriate environment variable:: $ export C_INCLUDE_PATH=/opt/local/include On Debian/Ubuntu, you can install libsmi with:: $ sudo apt-get install libffi-dev libsmi2-dev snmp-mibs-downloader On RedHat and similar, you can use:: $ sudo yum install libffi-devel libsmi-devel On OS X, if you are using homebrew_, you can use:: $ brew install libffi $ brew install libsmi .. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/ .. _homebrew: http://brew.sh On Debian and Ubuntu, *Snimpy* is also available as a package you can install with:: $ sudo apt-get install snimpy snimpy-0.8.13/docs/_static/0000755000076400001440000000000013360401663016140 5ustar bernatusers00000000000000snimpy-0.8.13/docs/_static/snimpy.svg0000644000076400001440000002744313012531051020177 0ustar bernatusers00000000000000 image/svg+xml snimpy-0.8.13/HISTORY.rst0000644000076400001440000000655613360042734015471 0ustar bernatusers00000000000000.. :changelog: History ------- 0.8.13 (2018-10-12) +++++++++++++++++++ * Compatibility with Python 3.7. * Fix an issue with implied index when reusing indexes between tables. 0.8.12 (2017-10-02) +++++++++++++++++++ * Support for more recent versions of IPython. * Support for SNMPv3 context name. * Support for notification nodes (MIB only). 0.8.11 (2016-08-13) +++++++++++++++++++ * Fix IPython interactive shell. * Fix IPv6 handling for sessions. * Ability for a session to return None instead of raising an exception. 0.8.10 (2016-02-16) +++++++++++++++++++ * Ability to walk a table (if the first index is accessible). * Ability to do a partial walk (courtesy of Alex Unigovsky). 0.8.8 (2015-11-15) ++++++++++++++++++ * Fix thread-safety problem introduced in 0.8.6. This also undo any improvement advertised in 0.8.6 when using multiple threads. However, performance should be kept when using a single thread. 0.8.7 (2015-11-14) ++++++++++++++++++ * Ability to specify a module name when querying a manager. * Compatibility with PySNMP 4.3 * Array-like interface for OIDs. * Ability to restrict lookups to a specific MIB: m['IF-MIB'].ifDescr. * Fix multithread support with SNMPv3 (with a performance impact). 0.8.6 (2015-06-24) ++++++++++++++++++ * Major speed improvement. * Major memory usage improvement. 0.8.5 (2015-04-04) ++++++++++++++++++ * Ability to set SMI search path (with ``mib.path()``) * Fix documentation build on *Read the Doc*. * Add a loose mode to manager to loosen type coercion. 0.8.4 (2015-02-10) ++++++++++++++++++ * More CFFI workarounds, including cross-compilation support. * Ability to override a node type. * Automatic workaround for "SNMP too big" error message. 0.8.3 (2014-08-18) ++++++++++++++++++ * IPv6 support. 0.8.2 (2014-06-08) ++++++++++++++++++ * Minor bugfixes. 0.8.1 (2013-10-25) ++++++++++++++++++ * Workaround a problem with CFFI extension installation. 0.8.0 (2013-10-19) ++++++++++++++++++++ * Python 3.3 support. Pypy support. * PEP8 compliant. * Sphinx documentation. * Octet strings with a display hint are now treated differently than plain octet strings (unicode). Notably, they can now be set using the displayed format (for example, for MAC addresses). 0.7.0 (2013-09-23) ++++++++++++++++++ * Major rewrite. * SNMP support is now provided through PySNMP_. * MIB parsing is still done with libsmi_ but through CFFI instead of a C module. * More unittests. Many bugfixes. .. _PySNMP: http://pysnmp.sourceforge.net/ .. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/ 0.6.4 (2013-03-21) ++++++++++++++++++ * GETBULK support. * MacAddress SMI type support. 0.6.3 (2012-04-13) ++++++++++++++++++ * Support for IPython 0.12. * Minor bugfixes. 0.6.2 (2012-01-19) ++++++++++++++++++ * Ability to return None instead of getting an exception. 0.6.1 (2012-01-14) ++++++++++++++++++ * Thread safety and efficiency. 0.6 (2012-01-10) ++++++++++++++++++ * SNMPv3 support 0.5.1 (2011-08-07) ++++++++++++++++++ * Compatibility with IPython 0.11. * Custom timeouts and retries. 0.5 (2010-02-03) ++++++++++++++++++ * Check conformity of loaded modules. * Many bugfixes. 0.4 (2009-06-06) ++++++++++++++++++ * Allow to cache requests. 0.3 (2008-11-23) ++++++++++++++++++ * Provide a manual page. * Use a context manager to group SET requests. 0.2.1 (2008-09-28) ++++++++++++++++++ * First release on PyPI. snimpy-0.8.13/requirements.txt0000644000076400001440000000052713012531051017037 0ustar bernatusers00000000000000Babel==2.1.1 Jinja2==2.8 MarkupSafe==0.23 Pygments==2.0.2 Sphinx==1.3.1 alabaster==0.7.6 cffi==1.3.0 coverage==4.0.2 docutils==0.12 flake8==2.3.0 mccabe==0.3.1 mock==1.3.0 pbr==1.8.1 pep8==1.6.2 pyasn1==0.1.9 pycparser==2.14 pycrypto==2.6.1 pyflakes==1.0.0 pysnmp==4.2.5 pytz==2015.7 six==1.10.0 snowballstemmer==1.2.0 sphinx-rtd-theme==0.1.9 snimpy-0.8.13/setup.cfg0000644000076400001440000000004613360401663015403 0ustar bernatusers00000000000000[egg_info] tag_build = tag_date = 0 snimpy-0.8.13/setup.py0000644000076400001440000000474413012531051015272 0ustar bernatusers00000000000000import os import sys from setuptools import setup from setuptools.command.test import test import snimpy rtd = os.environ.get('READTHEDOCS', None) == 'True' class SnimpyTestCommand(test): def run_tests(self, *args, **kwds): # Ensure we keep a reference to multiprocessing and pysnmp to # avoid errors at the end of the test import multiprocessing import pysnmp SnimpyTestCommand.multiprocessing = multiprocessing SnimpyTestCommand.pysnmp = pysnmp return test.run_tests(self, *args, **kwds) if __name__ == "__main__": readme = open('README.rst').read() history = open('HISTORY.rst').read().replace('.. :changelog:', '') setup(name="snimpy", classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: ISC License (ISCL)', 'Operating System :: POSIX', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: System :: Networking', 'Topic :: Utilities', 'Topic :: System :: Monitoring' ], url='https://github.com/vincentbernat/snimpy', description=snimpy.__doc__, long_description=readme + '\n\n' + history, author=snimpy.__author__, author_email=snimpy.__email__, packages=["snimpy"], entry_points={ 'console_scripts': [ 'snimpy = snimpy.main:interact', ], }, data_files=[('share/man/man1', ['man/snimpy.1'])], zip_safe=False, cffi_modules=(not rtd and ["snimpy/smi_build.py:ffi"] or []), install_requires=["cffi >= 1.0.0", "pysnmp >= 4", "setuptools"], setup_requires=["cffi >= 1.0.0", "vcversioner"], tests_require=list(filter(None, ["cffi >= 1.0.0", "pysnmp >= 4", "nose", "mock", sys.version_info < (2, 7) and "unittest2"])), test_suite="nose.collector", cmdclass={ "test": SnimpyTestCommand }, pbr=False, vcversioner={ 'version_module_paths': ['snimpy/_version.py'], }, ) snimpy-0.8.13/man/0000755000076400001440000000000013360401663014335 5ustar bernatusers00000000000000snimpy-0.8.13/man/snimpy.10000644000076400001440000000176713012531051015736 0ustar bernatusers00000000000000.TH SNIMPY 1 "Oct 4, 2008" .SH NAME snimpy \- interactive SNMP tool with Python .SH SYNOPSIS .B snimpy .RI [ options ] .SH DESCRIPTION This manual page documents briefly the .B snimpy command. .PP \fBsnimpy\fP is a Python-based tool providing a simple interface to build SNMP queries. This interface aims at being the most Pythonic possible: you grab scalars using attributes and columns are like dictionaries. .PP \fBsnimpy\fP can be used either interactively through its console (derived from Python own console or from IPython if available) or by writing \fBsnimpy\fP scripts which are just Python scripts with some global variables available. .SH OPTIONS \fBsnimpy\fP does not take any option. If you launch it without any argument, you will get the interactive console. Otherwise, the first argument is the name of a script to be executed and the remaining arguments are the arguments for this script. .SH SEE ALSO .nf https://github.com/vincentbernat/snimpy .fi .SH AUTHOR Vincent Bernat snimpy-0.8.13/MANIFEST.in0000644000076400001440000000066513012531051015314 0ustar bernatusers00000000000000recursive-include examples *.py recursive-include tests *.py SNIMPY-MIB.mib SNIMPY-INVALID-MIB.mib # Documentation include man/snimpy.1 include AUTHORS.rst include CONTRIBUTING.rst include HISTORY.rst include README.rst include version.txt recursive-include docs *.rst *.py recursive-include docs/_themes * recursive-include docs/_static * # Remove CFFI files global-exclude __pycache__/* # Remove git directories global-exclude .git snimpy-0.8.13/.travis.yml0000644000076400001440000000167113360036002015667 0ustar bernatusers00000000000000language: python sudo: require matrix: include: - python: "2.7" env: TOX_ENV=py27 - python: "2.7" env: TOX_ENV=py27-pysnmp42 - python: "3.4" env: TOX_ENV=py34 - python: "3.5" env: TOX_ENV=py35 - python: "3.5" env: TOX_ENV=py35-pysnmp42 - python: "3.5" env: TOX_ENV=py35-ipython - python: "3.6" env: TOX_ENV=py36 - python: "3.7" env: TOX_ENV=py37 dist: xenial - python: "pypy" env: TOX_ENV=pypy - python: "pypy" env: TOX_ENV=pypy-pysnmp42 - python: "3.6" env: TOX_ENV=lint - python: "3.6" env: TOX_ENV=doc addons: apt: packages: - pkg-config - libsmi2-dev - libsnmp-dev - snmp-mibs-downloader - ncurses-term install: - pip install tox coveralls before_script: - sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' script: - tox -e $TOX_ENV after_success: - coveralls snimpy-0.8.13/PKG-INFO0000644000076400001440000001725713360401663014673 0ustar bernatusers00000000000000Metadata-Version: 1.1 Name: snimpy Version: 0.8.13 Summary: interactive SNMP tool Home-page: https://github.com/vincentbernat/snimpy Author: Vincent Bernat Author-email: bernat@luffy.cx License: UNKNOWN Description: =============================== snimpy =============================== .. image:: https://badge.fury.io/py/snimpy.png :target: http://badge.fury.io/py/snimpy .. image:: https://travis-ci.org/vincentbernat/snimpy.png?branch=master :target: https://travis-ci.org/vincentbernat/snimpy .. image:: https://coveralls.io/repos/vincentbernat/snimpy/badge.png :target: https://coveralls.io/r/vincentbernat/snimpy Interactive SNMP tool. *Snimpy* is a Python-based tool providing a simple interface to build SNMP query. Here is a very simplistic example that allows us to display the routing table of a given host:: load("IP-FORWARD-MIB") m=M("localhost", "public", 2) routes = m.ipCidrRouteNextHop for x in routes: net, netmask, tos, src = x print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src)) You can either use *Snimpy* interactively throught its console (derived from Python own console or from IPython_ if available) or write *Snimpy* scripts which are just Python scripts with some global variables available. .. _IPython: http://ipython.org * Free software: ISC license * Documentation: http://snimpy.rtfd.org. *Snimpy* requires libsmi_ to work correctly. See the documentation for more information. .. _libsmi: https://www.ibr.cs.tu-bs.de/projects/libsmi/ Features -------- *Snimpy* is aimed at being the more Pythonic possible. You should forget that you are doing SNMP requests. *Snimpy* will rely on MIB to hide SNMP details. Here are some "features": * MIB parser based on libsmi (through CFFI) * SNMP requests are handled by PySNMP (SNMPv1, SNMPv2 and SNMPv3 support) * scalars are just attributes of your session object * columns are like a Python dictionary and made available as an attribute * getting an attribute is like issuing a GET method * setting an attribute is like issuing a SET method * iterating over a table is like using GETNEXT * when something goes wrong, you get an exception History ------- 0.8.13 (2018-10-12) +++++++++++++++++++ * Compatibility with Python 3.7. * Fix an issue with implied index when reusing indexes between tables. 0.8.12 (2017-10-02) +++++++++++++++++++ * Support for more recent versions of IPython. * Support for SNMPv3 context name. * Support for notification nodes (MIB only). 0.8.11 (2016-08-13) +++++++++++++++++++ * Fix IPython interactive shell. * Fix IPv6 handling for sessions. * Ability for a session to return None instead of raising an exception. 0.8.10 (2016-02-16) +++++++++++++++++++ * Ability to walk a table (if the first index is accessible). * Ability to do a partial walk (courtesy of Alex Unigovsky). 0.8.8 (2015-11-15) ++++++++++++++++++ * Fix thread-safety problem introduced in 0.8.6. This also undo any improvement advertised in 0.8.6 when using multiple threads. However, performance should be kept when using a single thread. 0.8.7 (2015-11-14) ++++++++++++++++++ * Ability to specify a module name when querying a manager. * Compatibility with PySNMP 4.3 * Array-like interface for OIDs. * Ability to restrict lookups to a specific MIB: m['IF-MIB'].ifDescr. * Fix multithread support with SNMPv3 (with a performance impact). 0.8.6 (2015-06-24) ++++++++++++++++++ * Major speed improvement. * Major memory usage improvement. 0.8.5 (2015-04-04) ++++++++++++++++++ * Ability to set SMI search path (with ``mib.path()``) * Fix documentation build on *Read the Doc*. * Add a loose mode to manager to loosen type coercion. 0.8.4 (2015-02-10) ++++++++++++++++++ * More CFFI workarounds, including cross-compilation support. * Ability to override a node type. * Automatic workaround for "SNMP too big" error message. 0.8.3 (2014-08-18) ++++++++++++++++++ * IPv6 support. 0.8.2 (2014-06-08) ++++++++++++++++++ * Minor bugfixes. 0.8.1 (2013-10-25) ++++++++++++++++++ * Workaround a problem with CFFI extension installation. 0.8.0 (2013-10-19) ++++++++++++++++++++ * Python 3.3 support. Pypy support. * PEP8 compliant. * Sphinx documentation. * Octet strings with a display hint are now treated differently than plain octet strings (unicode). Notably, they can now be set using the displayed format (for example, for MAC addresses). 0.7.0 (2013-09-23) ++++++++++++++++++ * Major rewrite. * SNMP support is now provided through PySNMP_. * MIB parsing is still done with libsmi_ but through CFFI instead of a C module. * More unittests. Many bugfixes. .. _PySNMP: http://pysnmp.sourceforge.net/ .. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/ 0.6.4 (2013-03-21) ++++++++++++++++++ * GETBULK support. * MacAddress SMI type support. 0.6.3 (2012-04-13) ++++++++++++++++++ * Support for IPython 0.12. * Minor bugfixes. 0.6.2 (2012-01-19) ++++++++++++++++++ * Ability to return None instead of getting an exception. 0.6.1 (2012-01-14) ++++++++++++++++++ * Thread safety and efficiency. 0.6 (2012-01-10) ++++++++++++++++++ * SNMPv3 support 0.5.1 (2011-08-07) ++++++++++++++++++ * Compatibility with IPython 0.11. * Custom timeouts and retries. 0.5 (2010-02-03) ++++++++++++++++++ * Check conformity of loaded modules. * Many bugfixes. 0.4 (2009-06-06) ++++++++++++++++++ * Allow to cache requests. 0.3 (2008-11-23) ++++++++++++++++++ * Provide a manual page. * Use a context manager to group SET requests. 0.2.1 (2008-09-28) ++++++++++++++++++ * First release on PyPI. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: ISC License (ISCL) Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: System :: Networking Classifier: Topic :: Utilities Classifier: Topic :: System :: Monitoring snimpy-0.8.13/CONTRIBUTING.rst0000644000076400001440000000613013164503740016224 0ustar bernatusers00000000000000============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: Types of Contributions ---------------------- Report Bugs ~~~~~~~~~~~ Report bugs at https://github.com/vincentbernat/snimpy/issues. If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ~~~~~~~~ Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. Implement Features ~~~~~~~~~~~~~~~~~~ Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. Write Documentation ~~~~~~~~~~~~~~~~~~~ Snimpy could always use more documentation, whether as part of the official Snimpy docs, in docstrings, or even on the web in blog posts, articles, and such. Submit Feedback ~~~~~~~~~~~~~~~ The best way to send feedback is to file an issue at https://github.com/vincentbernat/snimpy/issues. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) Get Started! ------------ Ready to contribute? Here's how to set up `snimpy` for local development. 1. Fork the `snimpy` repo on GitHub. 2. Clone your fork locally:: $ git clone git@github.com:your_name_here/snimpy.git 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: $ mkvirtualenv snimpy $ cd snimpy/ $ python setup.py develop 4. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: $ flake8 snimpy tests $ python setup.py test $ tox To get flake8 and tox, just pip install them into your virtualenv. 6. Commit your changes and push your branch to GitHub:: $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature 7. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. 3. The pull request should work for Python 2.7, 3.3, 3.4, 3.5 and 3.6, and for PyPy. Check https://travis-ci.org/vincentbernat/snimpy/pull_requests and make sure that the tests pass for all supported Python versions. Tips ---- To run a subset of tests:: $ python -m nose tests/test_snmp.py snimpy-0.8.13/tox.ini0000644000076400001440000000105513360036002015065 0ustar bernatusers00000000000000[tox] envlist = py{26,27,33,34,35,36,37,py},py{27,35,36,37,py}-{pysnmp42,ipython},lint,doc [testenv] deps = coverage pysnmp42: pysnmp>=4.2,<4.3 pysnmp42: pyasn1<0.2 ipython: ipython commands = {envpython} {envbindir}/coverage run --source=snimpy setup.py test setenv = pypy: with_gmp=no [testenv:lint] basepython = python deps = flake8 whitelist_externals = make commands = make lint [testenv:doc] basepython = python changedir = docs deps = -rrequirements.txt whitelist_externals = make commands = make html READTHEDOCS=True snimpy-0.8.13/version.txt0000644000076400001440000000002613360401662016005 0ustar bernatusers000000000000000.8.13-0-gf167b07635efsnimpy-0.8.13/AUTHORS.rst0000644000076400001440000000020413012531051015422 0ustar bernatusers00000000000000Development Lead ---------------- * Vincent Bernat Contributors ------------ * Jakub Wroniecki * Julian Taylor snimpy-0.8.13/tests/0000755000076400001440000000000013360401663014724 5ustar bernatusers00000000000000snimpy-0.8.13/tests/SNIMPY-MIB.mib0000644000076400001440000003277613215222020017043 0ustar bernatusers00000000000000SNIMPY-MIB DEFINITIONS ::= BEGIN IMPORTS MODULE-IDENTITY, OBJECT-TYPE, IpAddress, Integer32, Gauge32, TimeTicks, Counter64, Counter32, mib-2 FROM SNMPv2-SMI DisplayString, TEXTUAL-CONVENTION, PhysAddress, TruthValue FROM SNMPv2-TC InetAddressType, InetAddress, InetAddressIPv4, InetAddressIPv6 FROM INET-ADDRESS-MIB IANAifType FROM IANAifType-MIB; snimpy MODULE-IDENTITY LAST-UPDATED "200809160000Z" ORGANIZATION "snimpy https://github.com/vincentbernat/snimpy" CONTACT-INFO "Lorem ipsum, etc, etc." DESCRIPTION "This is a test MIB module for snimpy." REVISION "200809160000Z" DESCRIPTION "Last revision" ::= { mib-2 45121 } OddInteger ::= TEXTUAL-CONVENTION DISPLAY-HINT "d-2" STATUS current DESCRIPTION "Testing fmt" SYNTAX INTEGER (6..18 | 20..23 | 27 | 28..1336) UnicodeString ::= TEXTUAL-CONVENTION DISPLAY-HINT "255t" STATUS current DESCRIPTION "Testing fmt" SYNTAX OCTET STRING (SIZE(0..255)) snimpyScalars OBJECT IDENTIFIER ::= { snimpy 1 } snimpyTables OBJECT IDENTIFIER ::= { snimpy 2 } snimpyTraps OBJECT IDENTIFIER ::= { snimpy 3 } snimpyIpAddress OBJECT-TYPE SYNTAX IpAddress MAX-ACCESS read-only STATUS current DESCRIPTION "An IP address" ::= { snimpyScalars 1 } snimpyString OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) MAX-ACCESS read-only STATUS current DESCRIPTION "An string to display" ::= { snimpyScalars 2 } snimpyInteger OBJECT-TYPE SYNTAX OddInteger MAX-ACCESS read-only STATUS current DESCRIPTION "An integer" ::= { snimpyScalars 3 } snimpyEnum OBJECT-TYPE SYNTAX INTEGER { up(1), down(2), testing(3) } MAX-ACCESS read-only STATUS current DESCRIPTION "An enumeration" ::= { snimpyScalars 4 } snimpyObjectId OBJECT-TYPE SYNTAX OBJECT IDENTIFIER MAX-ACCESS read-only STATUS current DESCRIPTION "An oid" ::= { snimpyScalars 5 } snimpyBoolean OBJECT-TYPE SYNTAX TruthValue MAX-ACCESS read-only STATUS current DESCRIPTION "A boolean" ::= { snimpyScalars 6 } snimpyCounter OBJECT-TYPE SYNTAX Counter32 MAX-ACCESS read-only STATUS current DESCRIPTION "A 32 bits counter" ::= { snimpyScalars 7 } snimpyGauge OBJECT-TYPE SYNTAX Gauge32 MAX-ACCESS read-only STATUS current DESCRIPTION "A 32 bits gauge" ::= { snimpyScalars 8 } snimpyTimeticks OBJECT-TYPE SYNTAX TimeTicks MAX-ACCESS read-only STATUS current DESCRIPTION "A timetick" ::= { snimpyScalars 9 } snimpyCounter64 OBJECT-TYPE SYNTAX Counter64 MAX-ACCESS read-only STATUS current DESCRIPTION "A 64-bit counter" ::= { snimpyScalars 10 } snimpyBits OBJECT-TYPE SYNTAX BITS { first(0), second(1), third(2), last(7), secondByte(8) } MAX-ACCESS read-only STATUS current DESCRIPTION "A bit field" ::= { snimpyScalars 11 } snimpyNotImplemented OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) MAX-ACCESS read-only STATUS current DESCRIPTION "An string to display (not implemented)" ::= { snimpyScalars 12 } snimpyOctetString OBJECT-TYPE SYNTAX OCTET STRING MAX-ACCESS read-only STATUS current DESCRIPTION "An string to display" ::= { snimpyScalars 13 } snimpyUnicodeString OBJECT-TYPE SYNTAX UnicodeString MAX-ACCESS read-only STATUS current DESCRIPTION "An unicode string to display" ::= { snimpyScalars 14 } snimpyMacAddress OBJECT-TYPE SYNTAX PhysAddress MAX-ACCESS read-only STATUS current DESCRIPTION "A MAC address" ::= { snimpyScalars 15 } snimpyMacAddressInvalid OBJECT-TYPE SYNTAX DisplayString MAX-ACCESS read-only STATUS current DESCRIPTION "A MAC address with invalid syntax" ::= { snimpyScalars 16 } -- A simple table snimpySimpleTable OBJECT-TYPE SYNTAX SEQUENCE OF SnimpySimpleEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A table" ::= { snimpyTables 1 } snimpySimpleEntry OBJECT-TYPE SYNTAX SnimpySimpleEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Entry for our simple table" INDEX { snimpySimpleIndex } ::= { snimpySimpleTable 1 } SnimpySimpleEntry ::= SEQUENCE { snimpySimpleIndex Integer32, snimpySimpleDescr DisplayString, snimpySimpleType IANAifType, snimpySimplePhys PhysAddress } snimpySimpleIndex OBJECT-TYPE SYNTAX Integer32 (1..30) MAX-ACCESS not-accessible STATUS current DESCRIPTION "Index for snimpy simple table" ::= { snimpySimpleEntry 1 } snimpySimpleDescr OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) MAX-ACCESS read-only STATUS current DESCRIPTION "Blah blah" ::= { snimpySimpleEntry 2 } snimpySimpleType OBJECT-TYPE SYNTAX IANAifType MAX-ACCESS read-only STATUS current DESCRIPTION "Blah blah" ::= { snimpySimpleEntry 3 } snimpySimplePhys OBJECT-TYPE SYNTAX PhysAddress MAX-ACCESS read-only STATUS current DESCRIPTION "Blah blah" ::= { snimpySimpleEntry 4 } -- A more complex table snimpyComplexTable OBJECT-TYPE SYNTAX SEQUENCE OF SnimpyComplexEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A more complex table" ::= { snimpyTables 2 } snimpyComplexEntry OBJECT-TYPE SYNTAX SnimpyComplexEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Entry for our complex table" INDEX { snimpyComplexFirstIP, snimpyComplexSecondIP } ::= { snimpyComplexTable 1 } SnimpyComplexEntry ::= SEQUENCE { snimpyComplexFirstIP IpAddress, snimpyComplexSecondIP IpAddress, snimpyComplexState INTEGER } snimpyComplexFirstIP OBJECT-TYPE SYNTAX IpAddress MAX-ACCESS not-accessible STATUS current DESCRIPTION "First IP address for index" ::= { snimpyComplexEntry 1 } snimpyComplexSecondIP OBJECT-TYPE SYNTAX IpAddress MAX-ACCESS not-accessible STATUS current DESCRIPTION "Second IP address for index" ::= { snimpyComplexEntry 2 } snimpyComplexState OBJECT-TYPE SYNTAX INTEGER { up(1), down(2), testing(3) } MAX-ACCESS read-only STATUS current DESCRIPTION "State for our both IP" ::= { snimpyComplexEntry 3 } -- A table with complex indexes snimpyIndexTable OBJECT-TYPE SYNTAX SEQUENCE OF SnimpyIndexEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A table with complex indexes" ::= { snimpyTables 3 } snimpyIndexEntry OBJECT-TYPE SYNTAX SnimpyIndexEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Entry for our indexed table" INDEX { snimpyIndexVarLen, snimpyIndexOidVarLen, snimpyIndexFixedLen, IMPLIED snimpyIndexImplied } ::= { snimpyIndexTable 1 } SnimpyIndexEntry ::= SEQUENCE { snimpyIndexVarLen DisplayString, snimpyIndexIntIndex Integer32, snimpyIndexOidVarLen OBJECT IDENTIFIER, snimpyIndexFixedLen DisplayString, snimpyIndexImplied DisplayString, snimpyIndexInt Integer32 } snimpyIndexVarLen OBJECT-TYPE SYNTAX DisplayString (SIZE (1..10)) MAX-ACCESS read-only STATUS current DESCRIPTION "Variable length index" ::= { snimpyIndexEntry 1 } snimpyIndexIntIndex OBJECT-TYPE SYNTAX Integer32 MAX-ACCESS not-accessible STATUS current DESCRIPTION "Integer index" ::= { snimpyIndexEntry 2 } snimpyIndexOidVarLen OBJECT-TYPE SYNTAX OBJECT IDENTIFIER MAX-ACCESS not-accessible STATUS current DESCRIPTION "OID as index" ::= { snimpyIndexEntry 3 } snimpyIndexFixedLen OBJECT-TYPE SYNTAX DisplayString (SIZE (6)) MAX-ACCESS not-accessible STATUS current DESCRIPTION "Fixed length index" ::= { snimpyIndexEntry 4 } snimpyIndexImplied OBJECT-TYPE SYNTAX DisplayString (SIZE (1..30)) MAX-ACCESS not-accessible STATUS current DESCRIPTION "Variable length index, implied" ::= { snimpyIndexEntry 5 } snimpyIndexInt OBJECT-TYPE SYNTAX Integer32 MAX-ACCESS read-only STATUS current DESCRIPTION "An integer of fixed size" ::= { snimpyIndexEntry 6 } -- A table indexed using InetAddresses snimpyInetAddressTable OBJECT-TYPE SYNTAX SEQUENCE OF SnimpyInetAddressEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A InetAddress table" ::= { snimpyTables 4 } snimpyInetAddressEntry OBJECT-TYPE SYNTAX SnimpyInetAddressEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Entry for our complex table" INDEX { snimpyInetAddressType, snimpyInetAddress } ::= { snimpyInetAddressTable 1 } SnimpyInetAddressEntry ::= SEQUENCE { snimpyInetAddressType InetAddressType, snimpyInetAddress InetAddress, snimpyInetAddressState INTEGER } snimpyInetAddressType OBJECT-TYPE SYNTAX InetAddressType MAX-ACCESS not-accessible STATUS current DESCRIPTION "Address type identifier for snimpyInetAddress" ::= { snimpyInetAddressEntry 1 } snimpyInetAddress OBJECT-TYPE SYNTAX InetAddress MAX-ACCESS not-accessible STATUS current DESCRIPTION "Type dependent InetAddress" ::= { snimpyInetAddressEntry 2 } snimpyInetAddressState OBJECT-TYPE SYNTAX INTEGER { up(1), down(2), testing(3) } MAX-ACCESS read-only STATUS current DESCRIPTION "State for the IP" ::= { snimpyInetAddressEntry 3 } -- A table that may contain invalid values snimpyInvalidTable OBJECT-TYPE SYNTAX SEQUENCE OF SnimpyInvalidEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A table" ::= { snimpyTables 5 } snimpyInvalidEntry OBJECT-TYPE SYNTAX SnimpyInvalidEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Entry for our invalid table" INDEX { snimpyInvalidIndex } ::= { snimpyInvalidTable 1 } SnimpyInvalidEntry ::= SEQUENCE { snimpyInvalidIndex Integer32, snimpyInvalidDescr DisplayString } snimpyInvalidIndex OBJECT-TYPE SYNTAX Integer32 (1..30) MAX-ACCESS not-accessible STATUS current DESCRIPTION "Index for snimpy invalid table" ::= { snimpyInvalidEntry 1 } snimpyInvalidDescr OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) MAX-ACCESS read-only STATUS current DESCRIPTION "Blah blah" ::= { snimpyInvalidEntry 2 } -- A table that may be empty snimpyEmptyTable OBJECT-TYPE SYNTAX SEQUENCE OF SnimpyEmptyEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A table" ::= { snimpyTables 6 } snimpyEmptyEntry OBJECT-TYPE SYNTAX SnimpyEmptyEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Entry for our empty table" INDEX { snimpyEmptyIndex } ::= { snimpyEmptyTable 1 } SnimpyEmptyEntry ::= SEQUENCE { snimpyEmptyIndex Integer32, snimpyEmptyDescr DisplayString } snimpyEmptyIndex OBJECT-TYPE SYNTAX Integer32 (1..30) MAX-ACCESS not-accessible STATUS current DESCRIPTION "Index for snimpy empty table" ::= { snimpyEmptyEntry 1 } snimpyEmptyDescr OBJECT-TYPE SYNTAX DisplayString (SIZE (0..255)) MAX-ACCESS read-only STATUS current DESCRIPTION "Blah blah" ::= { snimpyEmptyEntry 2 } -- A table whose index re-uses objects from other tables' indexes snimpyReuseIndexTable OBJECT-TYPE SYNTAX SEQUENCE OF SnimpyReuseIndexEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A table whose index re-uses objects from other tables' indexes" ::= { snimpyTables 7 } snimpyReuseIndexEntry OBJECT-TYPE SYNTAX SnimpyReuseIndexEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Entry for our snimpyReuseIndexTable table note that snimpyIndexImplied is not the last item so it cannot be implied" INDEX { snimpyIndexImplied, snimpySimpleIndex } ::= { snimpyReuseIndexTable 1 } SnimpyReuseIndexEntry ::= SEQUENCE { snimpyReuseIndexValue INTEGER } snimpyReuseIndexValue OBJECT-TYPE SYNTAX INTEGER MAX-ACCESS read-only STATUS current DESCRIPTION "Integer value" ::= { snimpyReuseIndexEntry 1 } -- A notification snimpyNotification NOTIFICATION-TYPE OBJECTS { snimpySimpleIndex, snimpySimpleDescr, snimpySimpleType, snimpySimplePhys, snimpyIpAddress } STATUS current DESCRIPTION "A notification" ::= { snimpyTraps 1 } END snimpy-0.8.13/tests/agent.py0000644000076400001440000004421213357726113016405 0ustar bernatusers00000000000000from multiprocessing import Process, Queue import random from pysnmp.entity import engine, config from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.carrier.asynsock.dgram import udp, udp6 from pysnmp.proto.api import v2c class TestAgent(object): next_port = [random.randint(22000, 32000)] """Agent for testing purpose""" def __init__(self, ipv6=False, community='public', authpass='authpass', privpass='privpass', emptyTable=True): q = Queue() self.ipv6 = ipv6 self.emptyTable = emptyTable self.community = community self.authpass = authpass self.privpass = privpass self.next_port[0] += 1 self._process = Process(target=self._setup, args=(q, self.next_port[0])) self._process.start() self.port = q.get() def terminate(self): self._process.terminate() def _setup(self, q, port): """Setup a new agent in a separate process. The port the agent is listening too will be returned using the provided queue. """ snmpEngine = engine.SnmpEngine() if self.ipv6: config.addSocketTransport( snmpEngine, udp6.domainName, udp6.Udp6Transport().openServerMode(('::1', port))) else: config.addSocketTransport( snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(('127.0.0.1', port))) # Community is public and MIB is writable config.addV1System(snmpEngine, 'read-write', self.community) config.addVacmUser(snmpEngine, 1, 'read-write', 'noAuthNoPriv', (1, 3, 6), (1, 3, 6)) config.addVacmUser(snmpEngine, 2, 'read-write', 'noAuthNoPriv', (1, 3, 6), (1, 3, 6)) config.addV3User( snmpEngine, 'read-write', config.usmHMACMD5AuthProtocol, self.authpass, config.usmAesCfb128Protocol, self.privpass) config.addVacmUser(snmpEngine, 3, 'read-write', 'authPriv', (1, 3, 6), (1, 3, 6)) # Build MIB def stringToOid(string): return [ord(x) for x in string] def flatten(*args): result = [] for el in args: if isinstance(el, (list, tuple)): for sub in el: result.append(sub) else: result.append(el) return tuple(result) snmpContext = context.SnmpContext(snmpEngine) mibBuilder = snmpContext.getMibInstrum().getMibBuilder() (MibTable, MibTableRow, MibTableColumn, MibScalar, MibScalarInstance) = mibBuilder.importSymbols( 'SNMPv2-SMI', 'MibTable', 'MibTableRow', 'MibTableColumn', 'MibScalar', 'MibScalarInstance') class RandomMibScalarInstance(MibScalarInstance): previous_value = 0 def getValue(self, name, idx): self.previous_value += random.randint(1, 2000) return self.getSyntax().clone(self.previous_value) mibBuilder.exportSymbols( '__MY_SNMPv2_MIB', # SNMPv2-MIB::sysDescr MibScalar((1, 3, 6, 1, 2, 1, 1, 1), v2c.OctetString()), MibScalarInstance((1, 3, 6, 1, 2, 1, 1, 1), (0,), v2c.OctetString( "Snimpy Test Agent {0}".format( self.community))), # SNMPv2-MIB::sysObjectID MibScalar((1, 3, 6, 1, 2, 1, 1, 2), v2c.ObjectIdentifier()), MibScalarInstance((1, 3, 6, 1, 2, 1, 1, 2), (0,), v2c.ObjectIdentifier((1, 3, 6, 1, 4, 1, 9, 1, 1208)))) mibBuilder.exportSymbols( '__MY_IF_MIB', # IF-MIB::ifNumber MibScalar((1, 3, 6, 1, 2, 1, 2, 1), v2c.Integer()), MibScalarInstance((1, 3, 6, 1, 2, 1, 2, 1), (0,), v2c.Integer(3)), # IF-MIB::ifTable MibTable((1, 3, 6, 1, 2, 1, 2, 2)), MibTableRow((1, 3, 6, 1, 2, 1, 2, 2, 1)).setIndexNames( (0, '__MY_IF_MIB', 'ifIndex')), # IF-MIB::ifIndex MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (1,), v2c.Integer(1)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (2,), v2c.Integer(2)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (3,), v2c.Integer(3)), # IF-MIB::ifDescr MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 2), v2c.OctetString()), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (1,), v2c.OctetString("lo")), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (2,), v2c.OctetString("eth0")), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (3,), v2c.OctetString("eth1")), # IF-MIB::ifType MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 3), v2c.Integer()), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (1,), v2c.Integer(24)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (2,), v2c.Integer(6)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (3,), v2c.Integer(6)), # IF-MIB::ifInOctets MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 10), v2c.Integer()), RandomMibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 10), (1,), v2c.Gauge32()), RandomMibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 10), (2,), v2c.Gauge32()), RandomMibScalarInstance( (1, 3, 6, 1, 2, 1, 2, 2, 1, 10), (3,), v2c.Gauge32()), # IF-MIB::ifRcvAddressTable MibTable((1, 3, 6, 1, 2, 1, 31, 1, 4)), MibTableRow((1, 3, 6, 1, 2, 1, 31, 1, 4, 1)).setIndexNames( (0, '__MY_IF_MIB', 'ifIndex'), (1, '__MY_IF_MIB', 'ifRcvAddressAddress')), # IF-MIB::ifRcvAddressStatus MibTableColumn((1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 2), v2c.Integer()), MibScalarInstance( (1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 2), flatten(2, 6, stringToOid("abcdef")), v2c.Integer(1)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 2), flatten(2, 6, stringToOid("ghijkl")), v2c.Integer(1)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 2), flatten(3, 6, stringToOid("mnopqr")), v2c.Integer(1)), # IF-MIB::ifRcvAddressType MibTableColumn((1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 3), v2c.Integer()), MibScalarInstance( (1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 3), flatten(2, 6, stringToOid("abcdef")), v2c.Integer(1)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 3), flatten(2, 6, stringToOid("ghijkl")), v2c.Integer(1)), MibScalarInstance( (1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 3), flatten(3, 6, stringToOid("mnopqr")), v2c.Integer(1)), # IF-MIB::ifIndex ifIndex=MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 1), v2c.Integer()), # IF-MIB::ifRcvAddressAddress ifRcvAddressAddress=MibTableColumn((1, 3, 6, 1, 2, 1, 31, 1, 4, 1, 1), v2c.OctetString())) args = ( '__MY_SNIMPY-MIB', # SNIMPY-MIB::snimpyIpAddress MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 1), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 1), (0,), v2c.OctetString("AAAA")), # SNIMPY-MIB::snimpyString MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 2), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 2), (0,), v2c.OctetString("bye")), # SNIMPY-MIB::snimpyInteger MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 3), v2c.Integer()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 3), (0,), v2c.Integer(19)), # SNIMPY-MIB::snimpyEnum MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 4), v2c.Integer()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 4), (0,), v2c.Integer(2)), # SNIMPY-MIB::snimpyObjectId MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 5), v2c.ObjectIdentifier()).setMaxAccess("readwrite"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 5), ( 0,), v2c.ObjectIdentifier((1, 3, 6, 4454, 0, 0))), # SNIMPY-MIB::snimpyBoolean MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 6), v2c.Integer()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 6), (0,), v2c.Integer(1)), # SNIMPY-MIB::snimpyCounter MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 7), v2c.Counter32()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 7), (0,), v2c.Counter32(47)), # SNIMPY-MIB::snimpyGauge MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 8), v2c.Gauge32()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 8), (0,), v2c.Gauge32(18)), # SNIMPY-MIB::snimpyTimeticks MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 9), v2c.TimeTicks()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 9), (0,), v2c.TimeTicks(12111100)), # SNIMPY-MIB::snimpyCounter64 MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 10), v2c.Counter64()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 10), (0,), v2c.Counter64(2 ** 48 + 3)), # SNIMPY-MIB::snimpyBits MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 11), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 11), (0,), v2c.OctetString(b"\xa0\x80")), # SNIMPY-MIB::snimpyMacAddress MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 15), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 15), ( 0,), v2c.OctetString(b"\x11\x12\x13\x14\x15\x16")), # SNIMPY-MIB::snimpyMacAddressInvalid MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 16), v2c.OctetString()).setMaxAccess("readwrite"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 16), ( 0,), v2c.OctetString(b"\xf1\x12\x13\x14\x15\x16")), # SNIMPY-MIB::snimpyIndexTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 3)), MibTableRow( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1)).setIndexNames( (0, "__MY_SNIMPY-MIB", "snimpyIndexVarLen"), (0, "__MY_SNIMPY-MIB", "snimpyIndexOidVarLen"), (0, "__MY_SNIMPY-MIB", "snimpyIndexFixedLen"), (1, "__MY_SNIMPY-MIB", "snimpyIndexImplied")), # SNIMPY-MIB::snimpyIndexVarLen MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), flatten(4, stringToOid('row1'), 3, 1, 2, 3, stringToOid('alpha5'), stringToOid('end of row1')), v2c.OctetString(b"row1")), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), flatten(4, stringToOid('row2'), 4, 1, 0, 2, 3, stringToOid('beta32'), stringToOid('end of row2')), v2c.OctetString(b"row2")), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), flatten(4, stringToOid('row3'), 4, 120, 1, 2, 3, stringToOid('gamma7'), stringToOid('end of row3')), v2c.OctetString(b"row3")), # SNIMPY-MIB::snimpyIndexInt MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), flatten(4, stringToOid('row1'), 3, 1, 2, 3, stringToOid('alpha5'), stringToOid('end of row1')), v2c.Integer(4571)), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), flatten(4, stringToOid('row2'), 4, 1, 0, 2, 3, stringToOid('beta32'), stringToOid('end of row2')), v2c.Integer(78741)), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), flatten(4, stringToOid('row3'), 4, 120, 1, 2, 3, stringToOid('gamma7'), stringToOid('end of row3')), v2c.Integer(4110)), # SNIMPY-MIB::snimpyReuseIndexTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 7)), MibTableRow( (1, 3, 6, 1, 2, 1, 45121, 2, 7, 1)).setIndexNames( (0, "__MY_SNIMPY-MIB", "snimpyIndexImplied"), (0, "__MY_SNIMPY-MIB", "snimpySimpleIndex")), # SNIMPY-MIB::snimpyReuseIndexValue MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 7, 1, 1), flatten(11, stringToOid('end of row1'), 4), v2c.Integer(1785)), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 7, 1, 1), flatten(11, stringToOid('end of row1'), 5), v2c.Integer(2458)), # SNIMPY-MIB::snimpyInvalidTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 5)), MibTableRow( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1)).setIndexNames( (0, "__MY_SNIMPY-MIB", "snimpyInvalidIndex")), # SNIMPY-MIB::snimpyInvalidDescr MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), (1,), v2c.OctetString(b"Hello")), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), (2,), v2c.OctetString(b"\xf1\x12\x13\x14\x15\x16"))) if self.emptyTable: args += ( # SNIMPY-MIB::snimpyEmptyTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 6)), MibTableRow( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1)).setIndexNames( (0, "__MY_SNIMPY-MIB", "snimpyEmptyIndex"))) kwargs = dict( # Indexes snimpyIndexVarLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), v2c.OctetString( )), snimpyIndexIntIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 2), v2c.Integer( )).setMaxAccess( "noaccess"), snimpyIndexOidVarLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 3), v2c.ObjectIdentifier( )).setMaxAccess( "noaccess"), snimpyIndexFixedLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 4), v2c.OctetString( ).setFixedLength( 6)).setMaxAccess( "noaccess"), snimpyIndexImplied=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 5), v2c.OctetString( )).setMaxAccess("noaccess"), snimpyIndexInt=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), v2c.Integer()).setMaxAccess("readwrite"), snimpyInvalidIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 1), v2c.Integer()).setMaxAccess("noaccess"), snimpyInvalidDescr=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), v2c.OctetString()).setMaxAccess("readwrite"), snimpyReuseIndexValue=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 7, 1, 1), v2c.Integer()).setMaxAccess("readwrite") ) if self.emptyTable: kwargs.update(dict( snimpyEmptyIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1, 1), v2c.Integer()).setMaxAccess("noaccess"), snimpyEmptyDescr=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1, 2), v2c.OctetString()).setMaxAccess("readwrite"))) mibBuilder.exportSymbols(*args, **kwargs) # Start agent cmdrsp.GetCommandResponder(snmpEngine, snmpContext) cmdrsp.SetCommandResponder(snmpEngine, snmpContext) cmdrsp.NextCommandResponder(snmpEngine, snmpContext) cmdrsp.BulkCommandResponder(snmpEngine, snmpContext) q.put(port) snmpEngine.transportDispatcher.jobStarted(1) snmpEngine.transportDispatcher.runDispatcher() snimpy-0.8.13/tests/test_basictypes.py0000644000076400001440000006312113357726113020514 0ustar bernatusers00000000000000import unittest import os import re import socket import mock from datetime import timedelta from snimpy import mib, basictypes from pysnmp.proto import rfc1902 class TestBasicTypes(unittest.TestCase): def setUp(self): mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)), "SNIMPY-MIB.mib")) def tearDown(self): mib.reset() def testInteger(self): """Test integer basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyInteger", 18) self.assert_(isinstance(a, basictypes.Integer)) self.assertEqual(a, 18) self.assertEqual(a + 10, 28) a = basictypes.build("SNIMPY-MIB", "snimpyInteger", 4) self.assertEqual(a, 4) self.assertEqual(a * 4, 16) a = basictypes.build("SNIMPY-MIB", "snimpyInteger", 5) self.assertEqual(a, 5) self.assert_(a < 6) # self.assert_(a > 4.6) # type coercion does not work self.assert_(a > 4) self.assertRaises(TypeError, basictypes.build, ("SNIMPY-MIB", "snimpyInteger", [1, 2, 3])) def testString(self): """Test string basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"hello") self.assert_(isinstance(a, basictypes.String)) self.assertEqual(a, "hello") self.assertEqual(a + " john", "hello john") self.assertEqual(a * 2, "hellohello") a = basictypes.build("SNIMPY-MIB", "snimpyString", b"hello john") self.assert_("john" in a) self.assert_("steve" not in a) self.assertEqual(a[1], 'e') self.assertEqual(a[1:4], 'ell') self.assertEqual(len(a), 10) def testStringFromBytes(self): """Test string basic type when built from bytes""" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"hello") self.assert_(isinstance(a, basictypes.String)) self.assertEqual(a, "hello") self.assertEqual(a + " john", "hello john") self.assertEqual(a * 2, "hellohello") def testStringEncoding(self): """Test we can create an UTF-8 encoded string""" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"hello") self.assertEqual(a, u"hello") self.assertEqual(a, "hello") a = basictypes.build( "SNIMPY-MIB", "snimpyUnicodeString", u"\U0001F60E Hello") self.assertEqual(a, u"\U0001F60E Hello") a = basictypes.build( "SNIMPY-MIB", "snimpyUnicodeString", b'\xf0\x9f\x98\x8e Hello') self.assertEqual(a, u"\U0001F60E Hello") self.assertRaises(UnicodeError, basictypes.build, "SNIMPY-MIB", "snimpyString", b'\xf0\x9f\x98\x8e Hello') def testOctetString(self): """Test octet string basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyOctetString", b"hello\x41") self.assert_(isinstance(a, basictypes.OctetString)) self.assertEqual(a, b"hello\x41") self.assertEqual(len(a), 6) def testIpAddress(self): """Test IP address basic type""" a = basictypes.build( "SNIMPY-MIB", "snimpyIpAddress", socket.inet_aton("10.0.4.5")) self.assert_(isinstance(a, basictypes.IpAddress)) self.assertEqual(a, "10.0.4.5") self.assertEqual(a, "10.00.4.05") self.assertEqual(a, [10, 0, 4, 5]) self.assertEqual(a[2], 4) self.assert_(a < "10.1.2.4") self.assert_(a > "10.0.0.1") a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", [1, 2, 3, 5]) self.assertEqual(a, "1.2.3.5") a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", "10.0.4.5") self.assertEqual(a, "10.0.4.5") self.assertEqual(a, [10, 0, 4, 5]) a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", b"1001") self.assertEqual(a, [49, 48, 48, 49]) a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", b"0101") self.assertEqual(a, [48, 49, 48, 49]) a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", "100") self.assertEqual(a, [0, 0, 0, 100]) def testIncorrectIpAddress(self): """Test inappropriate IP addresses""" self.assertRaises(ValueError, basictypes.build, "SNIMPY-MIB", "snimpyIpAddress", "999.5.6.4") self.assertRaises(ValueError, basictypes.build, "SNIMPY-MIB", "snimpyIpAddress", "AAA") self.assertRaises(ValueError, basictypes.build, "SNIMPY-MIB", "snimpyIpAddress", "AAACC") def testEnum(self): """Test enum basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyEnum", 1) self.assert_(isinstance(a, basictypes.Enum)) self.assertEqual(a, 1) self.assertEqual(a, "up") a = basictypes.build("SNIMPY-MIB", "snimpyEnum", "down") self.assertEqual(a, "down") self.assert_(a != "up") self.assertEqual(a, 2) self.assertEqual(str(a), "down(2)") self.assertRaises(ValueError, basictypes.build, "SNIMPY-MIB", "snimpyEnum", "unknown") self.assertEqual(str(a), "down(2)") a = basictypes.build("SNIMPY-MIB", "snimpyEnum", 54) self.assertEqual(a, 54) def testOid(self): """Test OID basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyObjectId", mib.get("SNIMPY-MIB", "snimpyInteger")) self.assert_(isinstance(a, basictypes.Oid)) self.assertEqual(a, mib.get("SNIMPY-MIB", "snimpyInteger")) self.assertEqual(a, mib.get("SNIMPY-MIB", "snimpyInteger").oid) # Suboid self.assert_((list(mib.get("SNIMPY-MIB", "snimpyInteger").oid) + [2, 3]) in a) self.assert_((list(mib.get("SNIMPY-MIB", "snimpyInteger").oid)[:-1] + [29, 3]) not in a) # Ability to extract a component self.assertEqual(a[0], 1) self.assertEqual(a[1], 3) self.assertEqual(a[-3], 45121) self.assertEqual(a[-1], 3) self.assertEqual(a[:3], (1, 3, 6)) # Also accepts list a = basictypes.build("SNIMPY-MIB", "snimpyObjectId", (1, 2, 3, 4)) self.assertEqual(a, (1, 2, 3, 4)) self.assert_((1, 2, 3, 4, 5) in a) self.assert_((3, 4, 5, 6) not in a) def testBoolean(self): """Test boolean basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyBoolean", True) self.assert_(isinstance(a, basictypes.Boolean)) self.assertEqual(a, True) self.assert_(a) self.assert_(not(not(a))) self.assertEqual(not(a), False) a = basictypes.build("SNIMPY-MIB", "snimpyBoolean", "false") self.assertEqual(a, False) b = basictypes.build("SNIMPY-MIB", "snimpyBoolean", True) self.assertEqual(a or b, True) self.assertEqual(a and b, False) def testTimeticks(self): """Test timeticks basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyTimeticks", 676544) self.assert_(isinstance(a, basictypes.Timeticks)) # We can compare to int but otherwise, this is a timedelta self.assertEqual(a, 676544) self.assertEqual(str(a), '1:52:45.440000') self.assertEqual(a, timedelta(0, 6765, 440000)) a = basictypes.build("SNIMPY-MIB", "snimpyTimeticks", timedelta(1, 3)) self.assertEqual(str(a), '1 day, 0:00:03') self.assertEqual(a, (3 + 3600 * 24) * 100) self.assert_(a != (3 + 3600 * 24) * 100 + 1) self.assert_(a < timedelta(1, 4)) self.assert_(a > timedelta(1, 1)) self.assert_(a > 654) self.assert_(a >= 654) self.assert_(a < (3 + 3600 * 24) * 100 + 2) self.assertEqual(a, basictypes.build("SNIMPY-MIB", "snimpyTimeticks", timedelta(1, 3))) self.assert_(a < basictypes.build("SNIMPY-MIB", "snimpyTimeticks", timedelta(100, 30))) def testBits(self): """Test bit basic type""" a = basictypes.build("SNIMPY-MIB", "snimpyBits", [1, 2]) self.assert_(isinstance(a, basictypes.Bits)) self.assertEqual(a, [2, 1]) self.assertEqual(a, (1, 2)) self.assertEqual(a, set([1, 2])) self.assertEqual(a, ["second", "third"]) self.assertEqual(a, set(["second", "third"])) self.assertEqual(a, ["second", 2]) self.assert_(a != ["second"]) self.assertFalse(a == ["second"]) self.assertFalse(a != ["second", 2]) a |= "last" a |= ["last", "second"] self.assertEqual(a, ["second", "last", "third"]) self.assertEqual(str(a), "second(1), third(2), last(7)") a -= 1 a -= 1 self.assertEqual(a, ["last", "third"]) self.assertEqual(a & "last", True) self.assertEqual(a & "second", False) self.assertEqual(a & ["last", 2], True) self.assertEqual(a & set(["last", 2]), True) self.assertEqual(a & ["last", 0], True) self.assertEqual(a & ["second", 0], False) a = basictypes.build("SNIMPY-MIB", "snimpyBits", set(["first", "second"])) self.assertEqual(a, [0, 1]) a = basictypes.build("SNIMPY-MIB", "snimpyBits", []) self.assertEqual(a, []) self.assertEqual(str(a), "") def testInexistentBits(self): """Check we cannot set inexistent bits""" a = basictypes.build("SNIMPY-MIB", "snimpyBits", [1, 2]) self.assert_(a & 1) def nope(a): a |= 3 self.assertRaises(ValueError, nope, a) def testStringAsBits(self): """Test using bit specific operator with string""" a = basictypes.build( "SNIMPY-MIB", "snimpyOctetString", b"\x17\x00\x01") self.assert_(isinstance(a, basictypes.OctetString)) b = [7, 6, 5, 3, 23] for i in range(30): if i in b: self.assert_(a & i) else: self.assert_(not(a & i)) self.assert_(a & [5, 7]) self.assert_(not(a & [5, 9])) a |= [2, 10] a -= 22 a -= [23, 22] self.assert_(a & [2, 10]) self.assert_(not(a & 23)) self.assertEqual(a, b"\x37\x20\x00") a |= 31 self.assertEqual(a, b"\x37\x20\x00\x01") def testPacking(self): """Test pack() function""" self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyString", "Hello world").pack(), rfc1902.OctetString("Hello world")) self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyInteger", 18).pack(), rfc1902.Integer(18)) self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyInteger", 1804).pack(), rfc1902.Integer(1804)) self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyEnum", "testing").pack(), rfc1902.Integer(3)) self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyIpAddress", "10.11.12.13").pack(), rfc1902.IpAddress("10.11.12.13")) self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyObjectId", (1, 2, 3, 4)).pack(), rfc1902.univ.ObjectIdentifier((1, 2, 3, 4))) self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyTimeticks", timedelta(3, 2)).pack(), rfc1902.TimeTicks(3 * 3600 * 24 * 100 + 2 * 100)) self.assertEqual(basictypes.build("SNIMPY-MIB", "snimpyBits", [1, 7]).pack(), rfc1902.Bits(b"\x41")) def testOidConversion(self): """Test conversion to/from OID.""" tt = {("snimpySimpleIndex", 47, False): (47,), ("snimpyComplexFirstIP", "10.14.15.4", False): (10, 14, 15, 4), ("snimpyComplexSecondIP", (14, 15, 16, 17), False): (14, 15, 16, 17), ("snimpyIndexOidVarLen", (47, 48, 49), False): (3, 47, 48, 49), ("snimpyIndexVarLen", "hello1", False): tuple([len("hello1")] + [ord(a) for a in "hello1"]), ("snimpyIndexFixedLen", "hello2", False): tuple(ord(a) for a in "hello2"), ("snimpyIndexImplied", "hello3", True): tuple(ord(a) for a in "hello3"), ("snimpyIndexImplied", "hello3", False): tuple([len("hello3")] + [ord(a) for a in "hello3"]), } for key in tt: t, v, implied = key oid = basictypes.build("SNIMPY-MIB", t, v).toOid(implied) self.assertEqual(oid, tt[key]) # Test double conversion self.assertEqual(mib.get("SNIMPY-MIB", t).type.fromOid( mib.get("SNIMPY-MIB", t), oid, implied), (len(tt[key]), v)) def testTooLargeOid(self): """Handle the special case of octet string as OID with too large octets. See: https://github.com/vincentbernat/snimpy/pull/14 """ self.assertEqual(mib.get("SNIMPY-MIB", "snimpyIndexImplied").type.fromOid( mib.get("SNIMPY-MIB", "snimpyIndexImplied"), (104, 0xff00 | 101, 108, 108, 111), implied=True), (5, basictypes.build("SNIMPY-MIB", "snimpyIndexImplied", "hello"))) def testOidGreedy(self): """Test greediness of fromOid.""" tt = { ("snimpyIndexVarLen", False): ((5, 104, 101, 108, 108, 111, 111, 111, 111), (6, "hello")), ("snimpyIndexFixedLen", False): ((104, 101, 108, 108, 111, 49, 49, 111), (6, "hello1")), ("snimpyIndexImplied", True): ((104, 101, 108, 108, 111, 50), (6, "hello2")), ("snimpyIndexImplied", False): ((6, 104, 101, 108, 108, 111, 50), (7, "hello2")), ("snimpyComplexFirstIP", False): ((15, 15, 16, 100, 23, 74, 87), (4, "15.15.16.100")), ("snimpySimpleIndex", False): ((17, 19, 20), (1, 17)), ("snimpyIndexOidVarLen", False): ((3, 247, 145, 475568, 475, 263), (4, (247, 145, 475568))), } for key in tt: t, implied = key self.assertEqual(mib.get("SNIMPY-MIB", t).type.fromOid( mib.get("SNIMPY-MIB", t), tt[key][0], implied), tt[key][1]) # Test if too short tt = {"snimpyComplexFirstIP": ((17, 19, 20), False), "snimpyIndexFixedLen": ((104, 101, 108), False), "snimpyIndexVarLen": ((6, 102, 103, 104, 105), False), "snimpyIndexOidVarLen": ((3, 247, 145), False), "snimpyIndexImplied": ((3, 102, 103), False), } for t in tt: self.assertRaises(ValueError, mib.get("SNIMPY-MIB", t).type.fromOid, mib.get("SNIMPY-MIB", t), tt[t][0], tt[t][1]) def testDisplay(self): """Test string transformation""" self.assertEqual(str(basictypes.build("SNIMPY-MIB", "snimpyInteger", 18)), "0.18") self.assertEqual(str(basictypes.build("SNIMPY-MIB", "snimpyInteger", 8)), "0.08") self.assertEqual(str(basictypes.build("SNIMPY-MIB", "snimpyInteger", 288)), "2.88") self.assertEqual(str(basictypes.build("SNIMPY-MIB", "snimpyInteger", 28801)), "288.01") self.assertEqual(str(basictypes.build("SNIMPY-MIB", "snimpyString", "test")), "test") self.assertEqual(str(basictypes.build("SNIMPY-MIB", "snimpyOctetString", b"test")), str(b"test")) self.assertEqual(str(basictypes.build("SNIMPY-MIB", "snimpyOctetString", b"tes\x05")), str(b"tes\x05")) def testDisplayFormat(self): """Test display some with some formats""" with mock.patch("snimpy.mib.Node.fmt", new_callable=mock.PropertyMock) as e: e.return_value = "255a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(str(a), "test") a = basictypes.build("SNIMPY-MIB", "snimpyString", b"") self.assertEqual(str(a), "") e.return_value = "1x:" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(str(a), "74:65:73:74") e.return_value = "2a:" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(str(a), "te:st") e.return_value = "3a:" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(str(a), "tes:t") e.return_value = "4a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(str(a), "test") e.return_value = "2o+1a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(str(a), "072145+st") e.return_value = "*2a:+255a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"\x03testtest...") self.assertEqual(str(a), "te:st:te+st...") e.return_value = "2a1x:" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"aatest") self.assertEqual(str(a), "aa74:65:73:74") e.return_value = "*2a+1a:-*3a?=" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"\x04testtestZ\x02testes\x03testestes") self.assertEqual(str(a), "te+st+te+st+Z-tes?tes=tes?tes?tes") def testInputFormat(self): """Test we can input a string with a given format""" with mock.patch("snimpy.mib.Node.fmt", new_callable=mock.PropertyMock) as e: e.return_value = "255a" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"test") self.assertEqual(a.pack(), b"test") a = basictypes.build("SNIMPY-MIB", "snimpyString", b"") self.assertEqual(a.pack(), b"") e.return_value = "1x:" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"74:65:73:74") self.assertEqual(a.pack(), b"test") a = basictypes.build("SNIMPY-MIB", "snimpyString", u"74:6:73:4") self.assertEqual(a.pack(), b"t\x06s\x04") e.return_value = "2a:" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"te:st") self.assertEqual(a.pack(), b"test") e.return_value = "3a:" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"tes:t") self.assertEqual(a.pack(), b"test") e.return_value = "4a" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"test") self.assertEqual(a.pack(), b"test") e.return_value = "2o+1a" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"072145+st") self.assertEqual(a.pack(), b"test") e.return_value = "*2a:+255a" a = basictypes.build( "SNIMPY-MIB", "snimpyString", u"te:st:te+st...") self.assertEqual(a.pack(), b"\x03testtest...") e.return_value = "2a1x:" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"aa74:65:73:74") self.assertEqual(a.pack(), b"aatest") e.return_value = "*2a+@1a:-*3a?=" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"te+st+te+st@Z-tes?tes=tes?tes?tes") self.assertEqual(a.pack(), b"\x04testtestZ\x02testes\x03testestes") e.return_value = "3a" a = basictypes.build("SNIMPY-MIB", "snimpyString", u"a\n\r") self.assertEqual(a.pack(), b"a\n\r") def testRepr(self): """Test representation""" self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyInteger", 18)), "") self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyObjectId", (1, 3, 6, 1, 4, 1, 45, 3, 52, 1))), "") self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyIpAddress", "124.24.14.3")), "") self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyString", "45754dfgf")), "") self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyEnum", 2)), "") self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyBoolean", False)), "") self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyCounter", 4547)), "") self.assertEqual(repr(basictypes.build("SNIMPY-MIB", "snimpyBits", ["first", "second"])), "") def testEqualityWithDisplay(self): """Test we can check for equality with displayed form""" a = basictypes.build("SNIMPY-MIB", "snimpyString", "test") self.assertEqual(a, "test") with mock.patch("snimpy.mib.Node.fmt", new_callable=mock.PropertyMock) as e: e.return_value = "255a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(a, "test") e.return_value = "1x:" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(a, "74:65:73:74") e.return_value = "2a:" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(a, "te:st") e.return_value = "3a:" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(a, "tes:t") e.return_value = "4a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(a, "test") e.return_value = "2o+1a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test") self.assertEqual(a, "072145+st") self.assertNotEqual(a, "072145+sta") self.assertFalse(a != "072145+st") e.return_value = "*2a:+255a" a = basictypes.build("SNIMPY-MIB", "snimpyString", b"\x03testtest...") self.assertEqual(a, "te:st:te+st...") def testEqualityUnicode(self): """Test that equality works for both unicode and bytes""" a = basictypes.build("SNIMPY-MIB", "snimpyString", "test") self.assertEqual(a, "test") a = basictypes.build("SNIMPY-MIB", "snimpyString", "test") self.assertEqual(a, u"test") def testLikeAString(self): """Test String is like str""" a = basictypes.build("SNIMPY-MIB", "snimpyString", "4521dgf") self.assert_(a.startswith("4521")) self.assertEqual(a.upper(), "4521DGF") self.assert_(re.match("[0-9]+[defg]+", a)) snimpy-0.8.13/tests/test_conf.py0000644000076400001440000000234513357726113017274 0ustar bernatusers00000000000000import unittest import os import tempfile from snimpy.config import Conf class TestConf(unittest.TestCase): """Test configuration loading""" def test_default_configuration(self): """Check we can load the default configuration""" conf = Conf() loaded = conf.load() self.assertEqual(conf, loaded) self.assertEqual(conf.mibs, []) self.assertEqual(conf.ipython, True) self.assertEqual(conf.prompt, "\033[1m[snimpy]>\033[0m ") def test_inexistent_configuration(self): conf = Conf().load("dontexist") self.assertEqual(conf.mibs, []) self.assertEqual(conf.ipython, True) def test_loading_custom_configuration(self): conffile = tempfile.NamedTemporaryFile(delete=False) try: conffile.write(""" mibs = [ "IF-MIB", "LLDP-MIB" ] ipython = False unknown = "hey!" """.encode("ascii")) conffile.close() conf = Conf().load(conffile.name) self.assertEqual(conf.mibs, ["IF-MIB", "LLDP-MIB"]) self.assertEqual(conf.unknown, "hey!") self.assertEqual(conf.ipython, False) self.assertEqual(conf.ipythonprofile, None) finally: os.unlink(conffile.name) snimpy-0.8.13/tests/test_manager.py0000644000076400001440000004744313357726113017771 0ustar bernatusers00000000000000import sys import os import time from datetime import timedelta from snimpy.manager import load, Manager, snmp import agent if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestManager(unittest.TestCase): @classmethod def setUpClass(cls): load('IF-MIB') load('SNMPv2-MIB') load(os.path.join(os.path.dirname(os.path.abspath(__file__)), "SNIMPY-MIB.mib")) cls.agent = agent.TestAgent() @classmethod def tearDownClass(cls): cls.agent.terminate() def setUp(self): self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port), community="public", version=2) self.session = self.manager._session class TestManagerGet(TestManager): """Test getting stuff from manager""" def testGetScalar(self): """Retrieve some simple scalar values""" self.assertEqual(self.manager.sysDescr, "Snimpy Test Agent public") self.assertEqual(self.manager.ifNumber, 3) def scalarGetAndCheck(self, name, value): self.assertEqual(getattr(self.manager, name), value) def testScalar_IpAddress(self): """Retrieve IpAdress as a scalar""" self.scalarGetAndCheck("snimpyIpAddress", "65.65.65.65") def testScalar_String(self): """Retrieve a String as a scalar""" self.scalarGetAndCheck("snimpyString", "bye") def testScalar_Integer(self): """Retrieve an Integer as a scalar""" self.scalarGetAndCheck("snimpyInteger", 19) def testScalar_Enum(self): """Retrieve an Enum as a scalar""" self.scalarGetAndCheck("snimpyEnum", "down") def testScalar_ObjectId(self): """Retrieve an ObjectId as a scalar""" self.scalarGetAndCheck("snimpyObjectId", (1, 3, 6, 4454, 0, 0)) def testScalar_Boolean(self): """Retrieve a Boolean as a scalar""" self.scalarGetAndCheck("snimpyBoolean", True) def testScalar_Counter(self): """Retrieve a Counter as a scalar""" self.scalarGetAndCheck("snimpyCounter", 47) self.scalarGetAndCheck("snimpyCounter64", 2 ** 48 + 3) def testScalar_Gauge(self): """Retrieve a Gauge as a scalar""" self.scalarGetAndCheck("snimpyGauge", 18) def testScalar_Timeticks(self): """Retrieve a TimeTicks as a scalar""" self.scalarGetAndCheck( "snimpyTimeticks", timedelta(days=1, hours=9, minutes=38, seconds=31)) def testScalar_Bits(self): """Retrieve Bits as a scalar""" self.scalarGetAndCheck("snimpyBits", ["first", "third", "secondByte"]) def testScalar_MacAddress(self): """Retrieve MacAddress as a scalar""" self.scalarGetAndCheck("snimpyMacAddress", "11:12:13:14:15:16") def testContains_IfDescr(self): """Test proxy column membership checking code""" self.assertEqual(2 in self.manager.ifDescr, True) # FIXME: this currently fails under TestManagerWithNone # self.assertEqual(10 in self.manager.ifDescr, # False) def testWalkIfDescr(self): """Test we can walk IF-MIB::ifDescr and IF-MIB::ifTpe""" results = [(idx, self.manager.ifDescr[idx], self.manager.ifType[idx]) for idx in self.manager.ifIndex] self.assertEqual(results, [(1, "lo", 24), (2, "eth0", 6), (3, "eth1", 6)]) def testWalkIfTable(self): """Test we can walk IF-MIB::ifTable""" results = [(idx, self.manager.ifDescr[idx], self.manager.ifType[idx]) for idx in self.manager.ifTable] self.assertEqual(results, [(1, "lo", 24), (2, "eth0", 6), (3, "eth1", 6)]) def testWalkNotAccessible(self): """Test we can walk a table with the first entry not accessible.""" list(self.manager.ifRcvAddressTable) def testWalkIfDescrWithoutBulk(self): """Walk IF-MIB::ifDescr without GETBULK""" self.session.bulk = False self.testWalkIfDescr() def testWalkIfTableWithoutBulk(self): """Walk IF-MIB::ifTable without GETBULK""" self.session.bulk = False self.testWalkIfTable() def testWalkComplexIndexes(self): """Test if we can walk a table with complex indexes""" results = [(idx, self.manager.snimpyIndexInt[idx]) for idx in self.manager.snimpyIndexInt] self.assertEqual(results, [(("row1", (1, 2, 3), "alpha5", "end of row1"), 4571), (("row2", (1, 0, 2, 3), "beta32", "end of row2"), 78741), (("row3", (120, 1, 2, 3), "gamma7", "end of row3"), 4110)]) def testWalkTableWithComplexIndexes(self): """Test if we can walk a table with complex indexes""" results = [(idx, self.manager.snimpyIndexInt[idx]) for idx in self.manager.snimpyIndexTable] self.assertEqual(results, [(("row1", (1, 2, 3), "alpha5", "end of row1"), 4571), (("row2", (1, 0, 2, 3), "beta32", "end of row2"), 78741), (("row3", (120, 1, 2, 3), "gamma7", "end of row3"), 4110)]) def testWalkReuseIndexes(self): """Test if we can walk a table with re-used indexes""" results = [(idx, self.manager.snimpyReuseIndexValue[idx]) for idx in self.manager.snimpyReuseIndexValue] self.assertEqual(results, [(("end of row1", 4), 1785), (("end of row1", 5), 2458)]) def testWalkTableWithReuseIndexes(self): """Test if we can walk a table with re-used indexes""" results = [(idx, self.manager.snimpyReuseIndexValue[idx]) for idx in self.manager.snimpyReuseIndexTable] self.assertEqual(results, [(("end of row1", 4), 1785), (("end of row1", 5), 2458)]) def testWalkPartialIndexes(self): """Test if we can walk a slice of a table given a partial index""" results = [(idx, self.manager.ifRcvAddressType[idx]) for idx in self.manager.ifRcvAddressStatus[2]] self.assertEqual(results, [((2, "61:62:63:64:65:66"), 1), ((2, "67:68:69:6a:6b:6c"), 1)]) results = [(idx, self.manager.ifRcvAddressType[idx]) for idx in self.manager.ifRcvAddressStatus[(3,)]] self.assertEqual(results, [((3, "6d:6e:6f:70:71:72"), 1)]) results = list(self.manager.ifRcvAddressType.iteritems(3)) self.assertEqual(results, [((3, "6d:6e:6f:70:71:72"), 1)]) def testWalkInvalidPartialIndexes(self): """Try to get a table slice with an incorrect index filter""" self.assertRaises(ValueError, lambda: list( self.manager.ifRcvAddressStatus.iteritems( (3, "6d:6e:6f:70:71:72")))) def testContains_Partial(self): """Test proxy column membership checking code with partial indexes""" self.assertEqual( "61:62:63:64:65:66" in self.manager.ifRcvAddressStatus[2], True) # FIXME: this currently fails under TestManagerWithNone # self.assertEqual( # "6d:6e:6f:70:71:72" in self.manager.ifRcvAddressStatus[2], # False) def testScalar_MultipleSubscripts(self): """Retrieve a scalar value using multiple subscript syntax (attr[x][y])""" self.assertEqual(self.manager.ifRcvAddressType[2]["67:68:69:6a:6b:6c"], 1) def testGetInexistentStuff(self): """Try to access stuff that does not exist on the agent""" self.assertRaises(snmp.SNMPNoSuchObject, getattr, self.manager, "snimpyNotImplemented") self.assertRaises(snmp.SNMPNoSuchObject, self.manager.ifName.__getitem__, 47) self.assertRaises(snmp.SNMPNoSuchInstance, self.manager.ifDescr.__getitem__, 47) def testAccessInexistentStuff(self): """Try to access stuff that don't exist in MIB""" self.assertRaises(AttributeError, getattr, self.manager, "iDoNotExist") def testAccessIncorrectIndex(self): """Try to access with incorrect indexes""" self.assertRaises(ValueError, self.manager.ifDescr.__getitem__, (47, 18)) self.assertRaises(ValueError, self.manager.ifDescr.__getitem__, "nothing") def testAccessEmptyTable(self): """Try to walk an empty table""" results = [(idx,) for idx in self.manager.snimpyEmptyDescr] self.assertEqual(results, []) def testAccessNotExistentTable(self): """Try to walk a non-existent table""" agent2 = agent.TestAgent(emptyTable=False) try: manager = Manager(host="127.0.0.1:{0}".format(agent2.port), community="public", version=2) [(idx,) for idx in manager.snimpyEmptyDescr] except snmp.SNMPNoSuchObject: pass # That's OK else: self.assertFalse("should raise SNMPNoSuchObject exception") finally: agent2.terminate() def testGetChangingStuff(self): """Get stuff with varying values""" initial = self.manager.ifInOctets[2] current = self.manager.ifInOctets[2] self.assertGreater(current, initial) class TestManagerRestrictModule(TestManager): """Test when we restrict modules to be used by a manager""" def testGetSpecificModule(self): """Get a scalar from a specific module only""" self.assertEqual(self.manager['IF-MIB'].ifNumber, 3) self.assertEqual(self.manager['SNMPv2-MIB'].sysDescr, "Snimpy Test Agent public") def testGetInexistentModule(self): """Get a scalar from a non loaded module""" self.assertRaises(KeyError, lambda: self.manager['IF-MIB2']) def testGetInexistentScalarFromModule(self): """Get a non-existent scalar from a specific module""" self.assertRaises(AttributeError, lambda: self.manager['IF-MIB'].sysDescr) class TestManagerSet(TestManager): """Test setting stuff from manager""" def testSetScalar(self): """Try to set a simple value""" self.manager.snimpyString = "hello" self.assertEqual(self.manager.snimpyString, "hello") def scalarSetAndCheck(self, name, value): setattr(self.manager, name, value) self.assertEqual(getattr(self.manager, name), value) def testScalar_IpAddress(self): """Retrieve IpAdress as a scalar""" self.scalarSetAndCheck("snimpyIpAddress", "165.255.65.65") def testScalar_String(self): """Retrieve a String as a scalar""" self.scalarSetAndCheck("snimpyString", "awesome !!!") def testScalar_Integer(self): """Retrieve an Integer as a scalar""" self.scalarSetAndCheck("snimpyInteger", 1900) def testScalar_Enum(self): """Retrieve an Enum as a scalar""" self.scalarSetAndCheck("snimpyEnum", "up") def testScalar_ObjectId(self): """Retrieve an ObjectId as a scalar""" self.scalarSetAndCheck("snimpyObjectId", (1, 3, 6, 4454, 19, 47)) def testScalar_Boolean(self): """Retrieve a Boolean as a scalar""" self.scalarSetAndCheck("snimpyBoolean", False) def testScalar_Counter(self): """Retrieve a Counter as a scalar""" self.scalarSetAndCheck("snimpyCounter", 4700) self.scalarSetAndCheck("snimpyCounter64", 2 ** 48 + 3 - 18) def testScalar_Gauge(self): """Retrieve a Gauge as a scalar""" self.scalarSetAndCheck("snimpyGauge", 180014) def testScalar_Timeticks(self): """Retrieve a TimeTicks as a scalar""" self.scalarSetAndCheck( "snimpyTimeticks", timedelta(days=1, hours=17, minutes=38, seconds=31)) def testScalar_Bits(self): """Retrieve Bits as a scalar""" self.scalarSetAndCheck("snimpyBits", ["first", "second", "secondByte"]) def testScalar_MacAddress(self): """Retrieve MAC address as a scala""" self.scalarSetAndCheck("snimpyMacAddress", "a0:b0:c0:d0:e:ff") def testNonScalarSet(self): """Check we can set a non-scalar value""" idx = ("row2", (1, 0, 2, 3), "beta32", "end of row2") self.manager.snimpyIndexInt[idx] = 1041 self.assertEqual(self.manager.snimpyIndexInt[idx], 1041) def testSetWithContext(self): """Set several values atomically (inside a context)""" with self.manager as m: m.snimpyString = "Noooooo!" m.snimpyInteger = 42 self.assertEqual(m.snimpyString, "Noooooo!") self.assertEqual(m.snimpyInteger, 42) def testSetWithContextAndAbort(self): """Check if writing several values atomically can be aborted""" try: with self.manager as m: m.snimpyString = "Abort sir!" m.snimpyInteger = 37 raise RuntimeError("Abort now!") except RuntimeError as e: self.assertEqual(str(e), "Abort now!") self.assertNotEqual(m.snimpyString, "Abort sir!") self.assertNotEqual(m.snimpyInteger, 37) def testSetInexistentStuff(self): """Try to access stuff that does not exist on the agent""" self.assertRaises(snmp.SNMPNotWritable, setattr, self.manager, "snimpyNotImplemented", "Hello") self.assertRaises(snmp.SNMPNotWritable, self.manager.ifName.__setitem__, 47, "Wouh") self.assertRaises(snmp.SNMPNotWritable, self.manager.ifDescr.__setitem__, 47, "Noooo") def testAccessInexistentStuff(self): """Try to access stuff that don't exist in MIB""" self.assertRaises(AttributeError, setattr, self.manager, "iDoNotExist", 47) def testAccessIncorrectIndex(self): """Try to access with incorrect indexes""" self.assertRaises(ValueError, self.manager.ifDescr.__setitem__, (47, 18), "Nooo") self.assertRaises(ValueError, self.manager.ifDescr.__setitem__, "nothing", "Neither") class TestManagerWithNone(TestManagerGet): """Test a manager answering None for inexistent stuff""" def setUp(self): self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port), community="public", version=2, none=True) self.session = self.manager._session._session def testGetInexistentStuff(self): """Try to access stuff that does not exist on the agent""" self.assertEqual(self.manager.snimpyNotImplemented, None) self.assertEqual(self.manager.ifName[47], None) self.assertEqual(self.manager.ifDescr[47], None) class TestCachingManager(TestManagerGet): """Test if caching manager works like regular manager""" def setUp(self): self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port), community="public", version=2, cache=1) self.session = self.manager._session._session def testGetChangingStuff(self): """Get stuff with varying values""" initial = self.manager.ifInOctets[2] current = self.manager.ifInOctets[2] self.assertEqual(current, initial) def testCacheFlush(self): """Test cache timeout is working as expected""" first1 = self.manager.ifInOctets[1] second1 = self.manager.ifInOctets[2] third1 = self.manager.ifInOctets[3] time.sleep(0.5) second2 = self.manager.ifInOctets[2] third2 = self.manager.ifInOctets[3] self.assertEqual(second1, second2) # timeout not reached self.assertEqual(third1, third2) # timeout not reached time.sleep(1) first2 = self.manager.ifInOctets[1] self.assertGreater(first2, first1) # timeout was reached class TestCachingManagerWithModificatons(TestManager): """Test if caching manager works with modifications""" def setUp(self): self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port), community="public", version=2, cache=1) self.session = self.manager._session._session def testCacheScalar(self): """Check that a scalar value is kept in cache""" original = self.manager.snimpyString self.manager.snimpyString = "Nooooo" self.assertEqual(self.manager.snimpyString, original) def testCacheNonScalar(self): """Check we can cache a non-scalar value""" idx = ("row2", (1, 0, 2, 3), "beta32", "end of row2") original = self.manager.snimpyIndexInt[idx] self.manager.snimpyIndexInt[idx] = 1041 self.assertEqual(self.manager.snimpyIndexInt[idx], original) def testCacheExpire(self): """Check the cache can expire""" self.manager.snimpyString = "Yeesss" time.sleep(1) self.assertEqual(self.manager.snimpyString, "Yeesss") class TestManagerInvalidValues(TestManager): """Test when the agent is returning invalid values""" def testInvalidValue(self): """Check if an invalid value raises an exception""" self.assertRaises(ValueError, getattr, self.manager, "snimpyMacAddressInvalid") def testInvalidValueInTable(self): """Check if an invalid value in a table raises an exception""" self.assertRaises(ValueError, self.manager.snimpyInvalidDescr.__getitem__, 2) def testInvalidValueWhileIterating(self): """Check if an invalid value while walking raises an exception""" self.assertRaises(ValueError, list, self.manager.snimpyInvalidDescr.iteritems()) class TestManagerLoose(TestManager): """Test when the agent is returning invalid values with loose mode""" def setUp(self): self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port), community="public", version=2, loose=True) self.session = self.manager._session def testInvalidValue(self): """Check if an invalid value is correctly returned""" self.assertEqual(self.manager.snimpyMacAddressInvalid, b"\xf1\x12\x13\x14\x15\x16") def testInvalidValueInTable(self): """Check if an invalid value in a table is correctly returned""" self.assertEqual(self.manager.snimpyInvalidDescr[1], "Hello") self.assertEqual(self.manager.snimpyInvalidDescr[2], b"\xf1\x12\x13\x14\x15\x16") def testInvalidValueWhileIterating(self): """Check if an invalid value while walking works""" self.assertEqual(list(self.manager.snimpyInvalidDescr.iteritems()), [(1, "Hello"), (2, b"\xf1\x12\x13\x14\x15\x16")]) snimpy-0.8.13/tests/SNIMPY-INVALID-MIB.mib0000644000076400001440000000116013012531051020110 0ustar bernatusers00000000000000SNIMPY-INVALID-MIB DEFINITIONS ::= BEGIN IMPORTS inexistentNode FROM INEXISTENT-SNIMPY-MIB ; invalidSnimpy MODULE-IDENTITY LAST-UPDATED "200809160000Z" ORGANIZATION "snimpy https://github.com/vincentbernat/snimpy" CONTACT-INFO "Lorem ipsum, etc, etc." DESCRIPTION "This is a test MIB module for snimpy." REVISION "200809160000Z" DESCRIPTION "Last revision" ::= { mib-2 45122 } invalidSnimpyNode OBJECT-TYPE SYNTAX INTEGER MAX-ACCESS read-only STATUS current DESCRIPTION "An integer" ::= { inexistentNode 1 } END snimpy-0.8.13/tests/test_main.py0000644000076400001440000000230113357726113017263 0ustar bernatusers00000000000000import sys import os import tempfile import code # noqa: F401 import mock import platform from snimpy.main import interact from multiprocessing import Process import agent if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestMain(unittest.TestCase): """Test the main shell""" @classmethod def setUpClass(cls): cls.agent = agent.TestAgent() @classmethod def tearDownClass(cls): cls.agent.terminate() @unittest.skipIf(platform.python_implementation() == "PyPy", "setupterm seems unreliable with Pypy") def test_loadfile(self): script = tempfile.NamedTemporaryFile(delete=False) try: script.write(""" load("IF-MIB") m = M(host="127.0.0.1:{0}", community="public", version=2) assert(m.ifDescr[1] == "lo") """.format(self.agent.port).encode("ascii")) script.close() with mock.patch("code.InteractiveInterpreter.write"): p = Process(target=interact, args=((script.name,),)) p.start() p.join() self.assertEqual(p.exitcode, 0) finally: os.unlink(script.name) snimpy-0.8.13/tests/test_mib.py0000644000076400001440000004453513357726114017126 0ustar bernatusers00000000000000import unittest import os import sys from snimpy import mib, basictypes PYTHON3 = sys.version_info >= (3, 0) if PYTHON3: unicode = str class TestMibSnimpy(unittest.TestCase): def setUp(self): mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)), "SNIMPY-MIB.mib")) self.nodes = ["snimpy", "snimpyScalars", "snimpyTables", "snimpyTraps"] self.nodes.sort() self.tables = ["snimpyComplexTable", "snimpyInetAddressTable", "snimpySimpleTable", "snimpyIndexTable", "snimpyInvalidTable", "snimpyEmptyTable", "snimpyReuseIndexTable"] self.tables.sort() self.columns = ["snimpyComplexFirstIP", "snimpyComplexSecondIP", "snimpySimpleIndex", "snimpyComplexState", "snimpyInetAddressType", "snimpyInetAddress", "snimpyInetAddressState", "snimpySimpleDescr", "snimpySimplePhys", "snimpySimpleType", "snimpyIndexVarLen", "snimpyIndexIntIndex", "snimpyIndexOidVarLen", "snimpyIndexFixedLen", "snimpyIndexImplied", "snimpyIndexInt", "snimpyInvalidIndex", "snimpyInvalidDescr", "snimpyEmptyIndex", "snimpyEmptyDescr", "snimpyReuseIndexValue" ] self.columns.sort() self.scalars = ["snimpyIpAddress", "snimpyString", "snimpyInteger", "snimpyEnum", "snimpyObjectId", "snimpyBoolean", "snimpyCounter", "snimpyGauge", "snimpyTimeticks", "snimpyCounter64", "snimpyBits", "snimpyNotImplemented", "snimpyOctetString", "snimpyUnicodeString", "snimpyMacAddress", "snimpyMacAddressInvalid"] self.scalars.sort() self.notifications = ["snimpyNotification"] self.notifications.sort() self.expected_modules = [u"SNMPv2-SMI", u"SNMPv2-TC", u"SNIMPY-MIB", u"INET-ADDRESS-MIB", u"IANAifType-MIB"] def tearDown(self): mib.reset() def testGetNodes(self): """Test that we can get all nodes""" nodes = mib.getNodes('SNIMPY-MIB') snodes = sorted([str(a) for a in nodes]) self.assertEqual(self.nodes, snodes) for n in nodes: self.assert_(isinstance(n, mib.Node)) def testGetTables(self): """Test that we can get all tables""" tables = mib.getTables('SNIMPY-MIB') stables = sorted([str(a) for a in tables]) self.assertEqual(self.tables, stables) for n in tables: self.assert_(isinstance(n, mib.Table)) def testGetColumns(self): """Test that we can get all columns""" columns = mib.getColumns('SNIMPY-MIB') scolumns = sorted([str(a) for a in columns]) self.assertEqual(self.columns, scolumns) for n in columns: self.assert_(isinstance(n, mib.Column)) def testGetScalars(self): """Test that we can get all scalars""" scalars = mib.getScalars('SNIMPY-MIB') sscalars = sorted([str(a) for a in scalars]) self.assertEqual(self.scalars, sscalars) for n in scalars: self.assert_(isinstance(n, mib.Scalar)) def testGetNotifications(self): """Test that we can get all notifications""" notifications = mib.getNotifications('SNIMPY-MIB') snotifications = sorted([str(a) for a in notifications]) self.assertEqual(self.notifications, snotifications) for n in notifications: self.assert_(isinstance(n, mib.Notification)) def testGet(self): """Test that we can get all named attributes""" for i in self.scalars: self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i) self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Scalar)) for i in self.tables: self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i) self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Table)) for i in self.columns: self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i) self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Column)) for i in self.nodes: self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i) self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Node)) for i in self.notifications: self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i) self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Notification)) def testGetByOid(self): """Test that we can get all named attributes by OID.""" for i in self.scalars: nodebyname = mib.get('SNIMPY-MIB', i) self.assertEqual(str(mib.getByOid(nodebyname.oid)), i) self.assert_(isinstance(mib.getByOid(nodebyname.oid), mib.Scalar)) for i in self.tables: nodebyname = mib.get('SNIMPY-MIB', i) self.assertEqual(str(mib.getByOid(nodebyname.oid)), i) self.assert_(isinstance(mib.getByOid(nodebyname.oid), mib.Table)) for i in self.columns: nodebyname = mib.get('SNIMPY-MIB', i) self.assertEqual(str(mib.getByOid(nodebyname.oid)), i) self.assert_(isinstance(mib.getByOid(nodebyname.oid), mib.Column)) for i in self.nodes: nodebyname = mib.get('SNIMPY-MIB', i) self.assertEqual(str(mib.getByOid(nodebyname.oid)), i) self.assert_(isinstance(mib.getByOid(nodebyname.oid), mib.Node)) for i in self.notifications: nodebyname = mib.get('SNIMPY-MIB', i) self.assertEqual(str(mib.getByOid(nodebyname.oid)), i) self.assert_(isinstance(mib.getByOid(nodebyname.oid), mib.Notification)) def testGetByOid_UnknownOid(self): """Test that unknown OIDs raise an exception.""" self.assertRaises(mib.SMIException, mib.getByOid, (255,)) def testGetType(self): """Test that _getType properly identifies known and unknown types.""" self.assertEqual(b"PhysAddress", mib.ffi.string(mib._getType("PhysAddress").name)) self.assertEqual(b"InetAddress", mib.ffi.string(mib._getType(b"InetAddress").name)) self.assertEqual(None, mib._getType("SomeUnknownType.kjgf")) self.assertEqual(None, mib._getType("snimpySimpleTable")) def testTableColumnRelation(self): """Test that we can get the column from the table and vice-versa""" for i in self.tables: table = mib.get('SNIMPY-MIB', i) for r in table.columns: self.assert_(isinstance(r, mib.Column)) self.assertEqual(str(r.table), str(i)) self.assert_(str(r).startswith(str(i).replace("Table", ""))) columns = sorted([str(rr) for rr in self.columns if str(rr).startswith(str(i).replace("Table", ""))]) tcolumns = [str(rr) for rr in table.columns] tcolumns.sort() self.assertEqual(columns, tcolumns) for r in self.columns: column = mib.get('SNIMPY-MIB', r) table = column.table self.assert_(isinstance(table, mib.Table)) prefix = str(table).replace("Table", "") self.assertEqual(prefix, str(r)[:len(prefix)]) def testTypes(self): """Test that we get the correct types""" tt = {"snimpyIpAddress": basictypes.IpAddress, "snimpyString": basictypes.OctetString, "snimpyOctetString": basictypes.OctetString, "snimpyUnicodeString": basictypes.OctetString, "snimpyMacAddress": basictypes.OctetString, "snimpyInteger": basictypes.Integer, "snimpyEnum": basictypes.Enum, "snimpyObjectId": basictypes.Oid, "snimpyBoolean": basictypes.Boolean, "snimpyCounter": basictypes.Unsigned32, "snimpyGauge": basictypes.Unsigned32, "snimpyTimeticks": basictypes.Timeticks, "snimpyCounter64": basictypes.Unsigned64, "snimpyBits": basictypes.Bits, "snimpySimpleIndex": basictypes.Integer, "snimpyComplexFirstIP": basictypes.IpAddress, "snimpyComplexSecondIP": basictypes.IpAddress, "snimpyComplexState": basictypes.Enum} for t in tt: self.assertEqual(mib.get('SNIMPY-MIB', t).type, tt[t]) # Also check we get an exception when no type available def call(): mib.get('SNIMPY-MIB', 'snimpySimpleTable').type self.assertRaises(mib.SMIException, call) def testRanges(self): tt = {"snimpyIpAddress": 4, "snimpyString": (0, 255), "snimpyOctetString": None, "snimpyInteger": [(6, 18), (20, 23), (27, 1336)], "snimpyEnum": None, "snimpyObjectId": None, "snimpyBoolean": None, "snimpyCounter": (0, 4294967295), "snimpyGauge": (0, 4294967295), "snimpyTimeticks": (0, 4294967295), "snimpyCounter64": (0, 18446744073709551615), "snimpyBits": None, "snimpySimpleIndex": (1, 30), "snimpyComplexFirstIP": 4, "snimpyComplexSecondIP": 4, "snimpyComplexState": None } for t in tt: self.assertEqual(mib.get('SNIMPY-MIB', t).ranges, tt[t]) def testEnums(self): """Test that we got the enum values correctly""" self.assertEqual(mib.get('SNIMPY-MIB', "snimpyInteger").enum, None) self.assertEqual(mib.get("SNIMPY-MIB", "snimpyEnum").enum, {1: "up", 2: "down", 3: "testing"}) self.assertEqual(mib.get("SNIMPY-MIB", "snimpyBits").enum, {0: "first", 1: "second", 2: "third", 7: "last", 8: "secondByte"}) def testIndexes(self): """Test that we can retrieve correctly the index of tables""" self.assertEqual( [str(i) for i in mib.get("SNIMPY-MIB", "snimpySimpleTable").index], ["snimpySimpleIndex"]) self.assertEqual( [str(i) for i in mib.get("SNIMPY-MIB", "snimpyComplexTable").index], ["snimpyComplexFirstIP", "snimpyComplexSecondIP"]) self.assertEqual( [str(i) for i in mib.get("SNIMPY-MIB", "snimpyInetAddressTable").index], ["snimpyInetAddressType", "snimpyInetAddress"]) def testImplied(self): """Check that we can get implied attribute for a given table""" self.assertEqual( mib.get("SNIMPY-MIB", 'snimpySimpleTable').implied, False) self.assertEqual( mib.get("SNIMPY-MIB", 'snimpyComplexTable').implied, False) self.assertEqual( mib.get("SNIMPY-MIB", 'snimpyIndexTable').implied, True) def testOid(self): """Test that objects are rooted at the correct OID""" oids = {"snimpy": (1, 3, 6, 1, 2, 1, 45121), "snimpyScalars": (1, 3, 6, 1, 2, 1, 45121, 1), "snimpyString": (1, 3, 6, 1, 2, 1, 45121, 1, 2), "snimpyInteger": (1, 3, 6, 1, 2, 1, 45121, 1, 3), "snimpyBits": (1, 3, 6, 1, 2, 1, 45121, 1, 11), "snimpyTables": (1, 3, 6, 1, 2, 1, 45121, 2), "snimpySimpleTable": (1, 3, 6, 1, 2, 1, 45121, 2, 1), "snimpySimplePhys": (1, 3, 6, 1, 2, 1, 45121, 2, 1, 1, 4), "snimpyComplexTable": (1, 3, 6, 1, 2, 1, 45121, 2, 2), "snimpyComplexState": (1, 3, 6, 1, 2, 1, 45121, 2, 2, 1, 3), } for o in oids: self.assertEqual(mib.get('SNIMPY-MIB', o).oid, oids[o]) def testLoadedMibNames(self): """Check that only expected modules were loaded.""" for module in self.expected_modules: self.assertTrue(module in list(mib.loadedMibNames())) def testLoadInexistantModule(self): """Check that we get an exception when loading an inexistant module""" self.assertRaises(mib.SMIException, mib.load, "idontexist.gfdgfdg") def testLoadInvalidModule(self): """Check that an obviously invalid module cannot be loaded""" path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "SNIMPY-INVALID-MIB.mib") self.assertRaises(mib.SMIException, mib.load, path) self.assertRaises(mib.SMIException, mib.getNodes, "SNIMPY-INVALID-MIB") self.assertRaises(mib.SMIException, mib.get, "SNIMPY-INVALID-MIB", "invalidSnimpyNode") def testAccesInexistantModule(self): """Check that we get an exception when querying inexistant module""" self.assertRaises(mib.SMIException, mib.getNodes, "idontexist.kjgf") self.assertRaises(mib.SMIException, mib.getScalars, "idontexist.kjgf") self.assertRaises(mib.SMIException, mib.getTables, "idontexist.kjgf") self.assertRaises(mib.SMIException, mib.getColumns, "idontexist.kjgf") def testFmt(self): """Check that we get FMT from types""" self.assertEqual(mib.get("SNIMPY-MIB", 'snimpySimplePhys').fmt, "1x:") self.assertEqual(mib.get("SNIMPY-MIB", 'snimpyInteger').fmt, "d-2") def testTypeOverrides(self): """Check that we can override a type""" table = mib.get("SNIMPY-MIB", "snimpyInetAddressTable") addrtype_attr = table.index[0] addr_attr = table.index[1] # Try overriding to IPv4 with a byte string name. addrtype = addrtype_attr.type(addrtype_attr, "ipv4") self.assertEqual(addrtype, "ipv4") addr_attr.typeName = b"InetAddressIPv4" ipv4 = u"127.0.0.1" ipv4_oid = (4, 127, 0, 0, 1) addr = addr_attr.type(addr_attr, ipv4) self.assertEqual(str(addr), ipv4) self.assertEqual(addr.toOid(), ipv4_oid) addr_len, addr = addr_attr.type.fromOid(addr_attr, ipv4_oid) self.assertEqual(addr_len, ipv4_oid[0] + 1) self.assertEqual(str(addr), ipv4) self.assertEqual(addr.toOid(), ipv4_oid) # Try both IPv6 and non-bytes name. addrtype = addrtype_attr.type(addrtype_attr, "ipv6") self.assertEqual(addrtype, "ipv6") addr_attr.typeName = u"InetAddressIPv6" # Snimpy does not use leading zeroes. ipv6 = u'102:304:506:708:90a:b0c:d0e:f01' ipv6_oid = (16, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x01) addr = addr_attr.type(addr_attr, ipv6) self.assertEqual(str(addr), ipv6) self.assertEqual(addr.toOid(), ipv6_oid) addr_len, addr = addr_attr.type.fromOid(addr_attr, ipv6_oid) self.assertEqual(addr_len, ipv6_oid[0] + 1) self.assertEqual(str(addr), ipv6) self.assertEqual(addr.toOid(), ipv6_oid) # Try a type from a different module (chosen because snmpwalk # prints IPv6 addresses incorrectly). ipv6_1xformat = u'1:2:3:4:5:6:7:8:9:a:b:c:d:e:f:1' addr_attr.typeName = "PhysAddress" addr = addr_attr.type(addr_attr, ipv6_1xformat) self.assertEqual(str(addr), ipv6_1xformat) self.assertEqual(addr.toOid(), ipv6_oid) # Try overriding back to the default. del addr_attr.typeName addr_len, addr = addr_attr.type.fromOid(addr_attr, ipv4_oid) self.assertEqual(bytes(addr), b"\x7f\0\0\1") def testTypeOverrides_Errors(self): table = mib.get("SNIMPY-MIB", "snimpyInetAddressTable") attr = table.index[1] # Value with the wrong type. self.assertRaises(AttributeError, setattr, attr, "typeName", None) # Unknown type. self.assertRaises(mib.SMIException, setattr, attr, "typeName", "SomeUnknownType.kjgf") # Incompatible basetype. self.assertRaises(mib.SMIException, setattr, attr, "typeName", "InetAddressType") # Parse error. attr.typeName = "InetAddressIPv4" self.assertRaises(ValueError, attr.type, attr, u"01:02:03:04") def testTypeName(self): """Check that we can get the current declared type name""" table = mib.get("SNIMPY-MIB", "snimpyInetAddressTable") attr = table.index[1] self.assertEqual(attr.typeName, b"InetAddress") attr.typeName = b"InetAddressIPv4" self.assertEqual(attr.typeName, b"InetAddressIPv4") attr.typeName = b"InetAddressIPv6" self.assertEqual(attr.typeName, b"InetAddressIPv6") attr = mib.get("SNIMPY-MIB", "snimpySimpleIndex") self.assertEqual(attr.typeName, b"Integer32") class TestSmi(unittest.TestCase): def testGetPath(self): """Test we can get default SMI path""" current = mib.path() self.assertTrue(type(current), unicode) self.assertNotEqual(mib.path(), u"") def testSetPath(self): """Test we can set path to some value""" original = mib.path() current = original + ":/some/other/directory" try: mib.path(current) self.assertEqual(mib.path(), current) finally: mib.path(original) snimpy-0.8.13/tests/test_snmp.py0000644000076400001440000003634413360042353017321 0ustar bernatusers00000000000000import unittest import os import threading import multiprocessing import platform from datetime import timedelta from snimpy import basictypes, snmp, mib import agent class TestSnmpRetriesTimeout(unittest.TestCase): """Live modification of retry and timeout values for a session""" def setUp(self): self.session = snmp.Session(host="localhost", community="public", version=2) def testGetRetries(self): """Get default retries value""" self.assertEqual(self.session.retries, 5) def testGetTimeout(self): """Get default timeout value""" self.assertEqual(self.session.timeout, 1000000) def testSetRetries(self): """Try to set a new retry value""" self.session.retries = 2 self.assertEqual(self.session.retries, 2) self.session.retries = 0 self.assertEqual(self.session.retries, 0) def testSetTimeout(self): """Try to set a new timeout value""" self.session.timeout = 500000 self.assertEqual(self.session.timeout, 500000) def testErrors(self): """Try invalid values for timeout and retries""" self.assertRaises(ValueError, setattr, self.session, "timeout", 0) self.assertRaises(ValueError, setattr, self.session, "timeout", -30) self.assertRaises(ValueError, setattr, self.session, "retries", -5) class TestSnmpSession(unittest.TestCase): """Test for session creation using SNMPv1/v2c/v3""" def testSnmpV1(self): """Check initialization of SNMPv1 session""" snmp.Session(host="localhost", community="public", version=1) def testSnmpV2(self): """Check initialization of SNMPv2 session""" snmp.Session(host="localhost", community="public", version=2) def testSnmpV3(self): """Check initialization of SNMPv3 session""" snmp.Session(host="localhost", version=3, secname="readonly", authprotocol="MD5", authpassword="authpass", privprotocol="AES", privpassword="privpass") def testSnmpV3Protocols(self): """Check accepted auth and privacy protocols""" for auth in ["MD5", "SHA"]: for priv in ["AES", "AES128", "DES"]: snmp.Session(host="localhost", version=3, secname="readonly", authprotocol=auth, authpassword="authpass", privprotocol=priv, privpassword="privpass") self.assertRaises(ValueError, snmp.Session, host="localhost", version=3, secname="readonly", authprotocol="NOEXIST", authpassword="authpass", privprotocol="AES", privpassword="privpass") self.assertRaises(ValueError, snmp.Session, host="localhost", version=3, secname="readonly", authprotocol="MD5", authpassword="authpass", privprotocol="NOEXIST", privpassword="privpass") def testRepresentation(self): """Test session representation""" s = snmp.Session(host="localhost", community="public", version=1) self.assertEqual(repr(s), "Session(host=localhost,version=1)") def testSnmpV3SecLevels(self): """Check accepted security levels""" auth = "MD5" priv = "DES" snmp.Session(host="localhost", version=3, secname="readonly", authprotocol=auth, authpassword="authpass", privprotocol=priv, privpassword="privpass") snmp.Session(host="localhost", version=3, secname="readonly", authprotocol=None, privprotocol=None) snmp.Session(host="localhost", version=3, secname="readonly", authprotocol=auth, authpassword="authpass", privprotocol=None) class TestSnmp1(unittest.TestCase): """ Test communication with an agent with SNMPv1. """ version = 1 @classmethod def addAgent(cls, community, auth, priv): a = agent.TestAgent(community=community, authpass=auth, privpass=priv) cls.agents.append(a) return a @classmethod def setUpClass(cls): mib.load('IF-MIB') mib.load('SNMPv2-MIB') cls.agents = [] cls.agent = cls.addAgent('public', 'public-authpass', 'public-privpass') def setUp(self): params = self.setUpSession(self.agent, 'public') self.session = snmp.Session(**params) def setUpSession(self, agent, password): return dict(host="127.0.0.1:{0}".format(agent.port), community=password, version=self.version) @classmethod def tearDownClass(cls): for a in cls.agents: a.terminate() def testGetString(self): """Get a string value""" ooid = mib.get('SNMPv2-MIB', 'sysDescr').oid + (0,) oid, a = self.session.get(ooid)[0] self.assertEqual(oid, ooid) self.assertEqual(a, b"Snimpy Test Agent public") def testGetInteger(self): """Get an integer value""" oid, a = self.session.get(mib.get('IF-MIB', 'ifNumber').oid + (0,))[0] self.assert_(a > 1) # At least lo and another interface def testGetEnum(self): """Get an enum value""" oid, a = self.session.get(mib.get('IF-MIB', 'ifType').oid + (1,))[0] self.assertEqual(a, 24) # This is software loopback b = basictypes.build('IF-MIB', 'ifType', a) self.assertEqual(b, "softwareLoopback") def testGetMacAddress(self): """Get a MAC address""" mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)), "SNIMPY-MIB.mib")) oid, a = self.session.get((1, 3, 6, 1, 2, 1, 45121, 1, 15, 0))[0] self.assertEqual(a, b"\x11\x12\x13\x14\x15\x16") b = basictypes.build('SNIMPY-MIB', 'snimpyMacAddress', a) self.assertEqual(b, "11:12:13:14:15:16") def testGetObjectId(self): """Get ObjectId.""" ooid = mib.get('SNMPv2-MIB', 'sysObjectID').oid + (0,) oid, a = self.session.get(ooid)[0] self.assertEqual(oid, ooid) self.assertEqual(a, (1, 3, 6, 1, 4, 1, 9, 1, 1208)) def testInexistant(self): """Get an inexistant value""" try: self.session.get((1, 2, 3)) self.assertFalse("we should have got an exception") except snmp.SNMPException as ex: self.assert_(isinstance(ex, snmp.SNMPNoSuchName) or isinstance(ex, snmp.SNMPNoSuchObject)) def testSetIpAddress(self): """Set IpAddress.""" self.setAndCheck('snimpyIpAddress', '10.14.12.12') def testSetString(self): """Set String.""" self.setAndCheck('snimpyString', 'hello') def testSetInteger(self): """Set Integer.""" self.setAndCheck('snimpyInteger', 1574512) def testSetEnum(self): """Set Enum.""" self.setAndCheck('snimpyEnum', 'testing') def testSetObjectId(self): """Set ObjectId.""" self.setAndCheck('snimpyObjectId', (1, 2, 3, 4, 5, 6)) def testSetCounter(self): """Set Counter.""" self.setAndCheck('snimpyCounter', 545424) def testSetGauge(self): """Set Gauge.""" self.setAndCheck('snimpyGauge', 4857544) def testSetBoolean(self): """Set Boolean.""" self.setAndCheck('snimpyBoolean', True) def testSetTimeticks(self): """Set Timeticks.""" self.setAndCheck('snimpyTimeticks', timedelta(3, 18)) def testSetBits(self): """Set Bits.""" self.setAndCheck('snimpyBits', ["third", "last"]) def testSetMacAddress(self): """Set a MAC address.""" self.setAndCheck('snimpyMacAddress', u"a0:b0:c0:d0:e:ff") oid, a = self.session.get((1, 3, 6, 1, 2, 1, 45121, 1, 15, 0))[0] # This is software loopback self.assertEqual(a, b"\xa0\xb0\xc0\xd0\x0e\xff") def setAndCheck(self, oid, value): """Set and check a value""" mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)), "SNIMPY-MIB.mib")) ooid = mib.get('SNIMPY-MIB', oid).oid + (0,) self.session.set(ooid, basictypes.build('SNIMPY-MIB', oid, value)) self.assertEqual( basictypes.build('SNIMPY-MIB', oid, self.session.get(ooid)[0][1]), basictypes.build('SNIMPY-MIB', oid, value)) def testMultipleGet(self): """Get multiple values at once""" ooid1 = mib.get('SNMPv2-MIB', 'sysDescr').oid + (0,) ooid2 = mib.get('IF-MIB', 'ifNumber').oid + (0,) ooid3 = mib.get('IF-MIB', 'ifType').oid + (1,) (oid1, a1), (oid2, a2), (oid3, a3) = self.session.get( ooid1, ooid2, ooid3) self.assertEqual(oid1, ooid1) self.assertEqual(oid2, ooid2) self.assertEqual(oid3, ooid3) self.assertEqual(a1, b"Snimpy Test Agent public") self.assert_(a2 > 1) b = basictypes.build('IF-MIB', 'ifType', a3) self.assertEqual(b, "softwareLoopback") def testBulk(self): """Try to set bulk to different values""" self.session.bulk = 32 self.assertEqual(self.session.bulk, 32) self.assertRaises(ValueError, setattr, self.session, "bulk", 0) self.assertRaises(ValueError, setattr, self.session, "bulk", -10) def testWalk(self): """Check if we can walk""" ooid = mib.get("IF-MIB", "ifDescr").oid results = self.session.walk(ooid) self.assertEqual(tuple(results), ((ooid + (1,), b"lo"), (ooid + (2,), b"eth0"), (ooid + (3,), b"eth1"))) def testSeveralSessions(self): """Test with two sessions""" agent2 = self.addAgent('private', 'private-authpass', 'private-privpass') params = self.setUpSession(agent2, 'private') session2 = snmp.Session(**params) ooid = mib.get('SNMPv2-MIB', 'sysDescr').oid + (0,) oid1, a1 = self.session.get(ooid)[0] oid2, a2 = session2.get(ooid)[0] self.assertEqual(oid1, ooid) self.assertEqual(oid2, ooid) self.assertEqual(a1, b"Snimpy Test Agent public") self.assertEqual(a2, b"Snimpy Test Agent private") @unittest.skipIf(platform.python_implementation() == "PyPy", "unreliable test with Pypy") def testMultipleThreads(self): """Test with multiple sessions in different threads.""" count = 20 agents = [] for i in range(count): agents.append(self.addAgent('community{0}'.format(i), 'community{0}-authpass'.format(i), 'community{0}-privpass'.format(i))) ooid = mib.get('SNMPv2-MIB', 'sysDescr').oid + (0,) threads = [] successes = [] failures = [] lock = multiprocessing.Lock() # Start one thread def run(i): params = self.setUpSession(agents[i], 'community{0}'.format(i)) session = snmp.Session(**params) session.timeout = 10 * 1000 * 1000 oid, a = session.get(ooid)[0] exp = ("Snimpy Test Agent community{0}".format(i)).encode('ascii') with lock: if oid == ooid and \ a == exp: successes.append("community{0}".format(i)) else: failures.append("community{0}".format(i)) for i in range(count): threads.append(threading.Thread(target=run, args=(i,))) for i in range(count): threads[i].start() for i in range(count): threads[i].join() self.assertEqual(failures, []) self.assertEqual(sorted(successes), sorted(["community{0}".format(i) for i in range(count)])) class TestSnmp2(TestSnmp1): """Test communication with an agent with SNMPv2.""" version = 2 def testInexistantNone(self): """Get an inexistant value but request none""" params = self.setUpSession(self.agent, 'public') params['none'] = True session = snmp.Session(**params) oid, a = session.get((1, 2, 3))[0] self.assertEqual(a, None) def testSetCounter64(self): """Set Counter64.""" self.setAndCheck('snimpyCounter64', 2 ** 47 + 1) def testWalk(self): """Check if we can walk""" ooid = mib.get("IF-MIB", "ifDescr").oid self.session.bulk = 4 results = self.session.walk(ooid) self.assertEqual(tuple(results), ((ooid + (1,), b"lo"), (ooid + (2,), b"eth0"), (ooid + (3,), b"eth1"))) self.session.bulk = 2 results = self.session.walk(ooid) self.assertEqual(tuple(results), ((ooid + (1,), b"lo"), (ooid + (2,), b"eth0"), (ooid + (3,), b"eth1"))) class TestSnmp3(TestSnmp2): """Test communicaton with an agent with SNMPv3.""" version = 3 def setUpSession(self, agent, password): return dict(host="127.0.0.1:{0}".format(agent.port), version=3, secname="read-write", authprotocol="MD5", authpassword="{0}-authpass".format(password), privprotocol="AES", privpassword="{0}-privpass".format(password)) class TestSnmpTransports(unittest.TestCase): """Test communication using IPv6.""" ipv6 = True @classmethod def setUpClass(cls): mib.load('IF-MIB') mib.load('SNMPv2-MIB') def _test(self, ipv6, host): m = agent.TestAgent(ipv6) session = snmp.Session( host="{0}:{1}".format(host, m.port), community="public", version=2) try: ooid = mib.get('SNMPv2-MIB', 'sysDescr').oid + (0,) oid, a = session.get(ooid)[0] self.assertEqual(a, b"Snimpy Test Agent public") finally: m.terminate() def testIpv4(self): """Test IPv4 transport""" self._test(False, "127.0.0.1") def testIpv4WithDNS(self): """Test IPv4 transport with name resolution""" self._test(False, "localhost") def testIpv6(self): """Test IPv6 transport""" self._test(True, "[::1]") snimpy-0.8.13/examples/0000755000076400001440000000000013360401663015400 5ustar bernatusers00000000000000snimpy-0.8.13/examples/vlan-and-interfaces.py0000644000076400001440000000116212272767417021610 0ustar bernatusers00000000000000#!/usr/bin/snimpy """ On Nortel switches, list vlan on all active ports """ import os import sys load("SNMPv2-MIB") load("IF-MIB") load(os.path.expanduser("~/.snmp/mibs/RAPID-CITY-MIB")) load(os.path.expanduser("~/.snmp/mibs/RC-VLAN-MIB")) s = M(host=sys.argv[1], community=sys.argv[2]) vlans = {} for interface in s.ifIndex: if s.ifOperStatus[interface] == "up": vlans[int(interface)] = [] for vlan in s.rcVlanId: for interface in vlans: if s.rcVlanStaticMembers[vlan] & interface: vlans[interface].append("%s(%s)" % (vlan, s.rcVlanName[vlan])) import pprint pprint.pprint(vlans) snimpy-0.8.13/examples/disable-port-ingress-filtering.py0000644000076400001440000000107213012531051023755 0ustar bernatusers00000000000000#!/usr/bin/snimpy """Disable port ingress filtering on Nortel switch (also known as filter-unregistered-frames).""" from __future__ import print_function import sys load("SNMPv2-MIB") load("Q-BRIDGE-MIB") s = M(host=sys.argv[1], community=sys.argv[2]) if "Ethernet Routing Switch 55" not in s.sysDescr: print("Not a 5510") sys.exit(1) for id in s.dot1qPortIngressFiltering: if s.dot1qPortIngressFiltering[id]: print("Filtering on port %d of %s is not disabled, disable it." % (id, sys.argv[1])) s.dot1qPortIngressFiltering[id] = False snimpy-0.8.13/examples/enable-lldp.py0000644000076400001440000000343013012531051020116 0ustar bernatusers00000000000000#!/usr/bin/snimpy """Enable LLDP. Generic procedure but we restrict ourself to Nortel 55x0. """ from __future__ import print_function import sys import os load("SNMPv2-MIB") for l in ["LLDP", "LLDP-EXT-DOT3", "LLDP-EXT-DOT1"]: load(os.path.expanduser("~/.snmp/mibs/%s-MIB" % l)) s = M(host=sys.argv[1], community=sys.argv[2]) try: type = s.sysDescr except snmp.SNMPException: print("Cannot process %s: bad community?" % sys.argv[1]) sys.exit(1) if not type.startswith("Ethernet Routing Switch 55") and \ not type.startswith("Ethernet Switch 425"): print("Not a 55x0: %s" % type) sys.exit(1) print("Processing %s..." % sys.argv[1]) try: for oid in s.lldpConfigManAddrPortsTxEnable: if oid[0] == "ipV4": s.lldpConfigManAddrPortsTxEnable[oid] = "\xff"*10 except snmp.SNMPNoSuchObject: print("No LLDP for this switch") sys.exit(2) dot3 = True for port in s.lldpPortConfigAdminStatus: s.lldpPortConfigAdminStatus[port] = "txAndRx" s.lldpPortConfigTLVsTxEnable[port] = ["portDesc", "sysName", "sysDesc", "sysCap" ] # Dot3 try: if dot3: s.lldpXdot3PortConfigTLVsTxEnable[port] = ["macPhyConfigStatus", "powerViaMDI", "linkAggregation", "maxFrameSize"] except snmp.SNMPException: print("No Dot3") dot3 = False # Dot1 try: for port,vlan in s.lldpXdot1ConfigVlanNameTxEnable: s.lldpXdot1ConfigVlanNameTxEnable[port, vlan] = True except snmp.SNMPException: print("No Dot1") print("Success!") snimpy-0.8.13/examples/add-vlan.py0000644000076400001440000000236313012531051017431 0ustar bernatusers00000000000000#!/usr/bin/snimpy """ On Nortel switches, create a new VLAN and tag it on "TagAll" ports. """ from __future__ import print_function import os import sys load("SNMPv2-MIB") load(os.path.expanduser("~/.snmp/mibs/RAPID-CITY-MIB")) load(os.path.expanduser("~/.snmp/mibs/RC-VLAN-MIB")) vlanNumber = int(sys.argv[3]) vlanName = sys.argv[4] s = M(host=sys.argv[1], community=sys.argv[2]) # Create the VLAN if vlanNumber not in s.rcVlanId: print("VLAN %d will be created with name %s on %s" % (vlanNumber, vlanName, sys.argv[1])) with s: s.rcVlanRowStatus[vlanNumber] = "createAndGo" s.rcVlanName[vlanNumber] = vlanName s.rcVlanType[vlanNumber] = "byPort" else: print("VLAN %d already exists on %s" % (vlanNumber, sys.argv[1])) # Just set the name if s.rcVlanName[vlanNumber] != vlanName: s.rcVlanName[vlanNumber] = vlanName # Which ports are tagall ? tagged = [port for port in s.rcVlanPortPerformTagging if s.rcVlanPortPerformTagging[port] ] if len(tagged) != 2 and len(tagged) != 3: print("%s does not have exactly two or three tagged ports (%r)" % (sys.argv[1], tagged)) sys.exit(1) print("VLAN %d will be tagged on ports %s" % (vlanNumber, tagged)) s.rcVlanStaticMembers[vlanNumber] |= tagged snimpy-0.8.13/examples/rename-vlan.py0000644000076400001440000000116013012531051020142 0ustar bernatusers00000000000000#!/usr/bin/snimpy from __future__ import print_function import os import sys load("SNMPv2-MIB") load(os.path.expanduser("~/.snmp/mibs/RAPID-CITY-MIB")) load(os.path.expanduser("~/.snmp/mibs/RC-VLAN-MIB")) vlanNumber = int(sys.argv[3]) newName = sys.argv[4] s = M(host=sys.argv[1], community=sys.argv[2]) try: cur = s.rcVlanName[vlanNumber] except snmp.SNMPException: print("%s is not a Nortel switch or does not have VLAN %d" % (sys.argv[1], vlanNumber)) sys.exit(1) if cur != newName: s.rcVlanName[vlanNumber] = newName print("Setting VLAN %d of %s as %s: done." % (vlanNumber, sys.argv[1], newName)) snimpy-0.8.13/examples/set-syslog-ntp.py0000755000076400001440000000424713012531051020661 0ustar bernatusers00000000000000#!/usr/bin/snimpy """ Set NTP or syslog server on various switches Usage: ./set-syslog-ntp.py [syslog|ntp] host community first [...] """ from __future__ import print_function import os import sys load("SNMPv2-MIB") host = sys.argv[2] targets = sys.argv[4:] operation = sys.argv[1] try: s = M(host=host, community=sys.argv[3]) sid = str(s.sysObjectID) except snmp.SNMPException, e: print("%s: %s" % (host, e)) sys.exit(1) if sid.startswith("1.3.6.1.4.1.45.3."): # Nortel print("%s is Nortel 55xx" % host) load(os.path.expanduser("~/.snmp/mibs/SYNOPTICS-ROOT-MIB")) load(os.path.expanduser("~/.snmp/mibs/S5-ROOT-MIB")) if operation == "ntp": load(os.path.expanduser("~/.snmp/mibs/S5-AGENT-MIB")) s.s5AgSntpPrimaryServerAddress = targets[0] if len(targets) > 1: s.s5AgSntpSecondaryServerAddress = targets[1] else: s.s5AgSntpSecondaryServerAddress = "0.0.0.0" s.s5AgSntpState = "unicast" s.s5AgSntpManualSyncRequest = "requestSync" elif operation == "syslog": load(os.path.expanduser("~/.snmp/mibs/BN-LOG-MESSAGE-MIB")) s.bnLogMsgRemoteSyslogAddress = targets[0] s.bnLogMsgRemoteSyslogSaveTargets = "msgTypeInformational" s.bnLogMsgRemoteSyslogEnabled = True elif sid.startswith("1.3.6.1.4.1.1872."): print("%s is Alteon" % host) load(os.path.expanduser("~/.snmp/mibs/ALTEON-ROOT-MIB")) if operation == "ntp": s.agNewCfgNTPServer = targets[0] if len(targets) > 1: s.agNewCfgNTPSecServer = targets[1] else: s.agNewCfgNTPSecServer = "0.0.0.0" s.agNewCfgNTPService = "enabled" elif operation == "syslog": s.agNewCfgSyslogHost = targets[0] s.agNewCfgSyslogFac = "local2" if len(targets) > 1: s.agNewCfgSyslog2Host = targets[1] s.agNewCfgSyslog2Fac = "local2" else: s.agNewCfgSyslog2Host = "0.0.0.0" if s.agApplyPending == "applyNeeded": if s.agApplyConfig == "complete": s.agApplyConfig = "idle" s.agApplyConfig = "apply" else: print("%s is unknown (%s)" % (host, s.sysDescr)) sys.exit(1) snimpy-0.8.13/examples/list-routes.py0000644000076400001440000000121213012531051020225 0ustar bernatusers00000000000000#!/usr/bin/snimpy from __future__ import print_function from socket import inet_ntoa load("IP-FORWARD-MIB") m=M() print("Using IP-FORWARD-MIB::ipCidrRouteTable...") routes = m.ipCidrRouteNextHop for x in routes: net, netmask, tos, src = x print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src)) print print("Using IP-FORWARD-MIB::inetCidrRouteTable...") routes = m.inetCidrRouteIfIndex for x in routes: dsttype, dst, prefix, oid, nhtype, nh = x if dsttype != "ipv4" or nhtype != "ipv4": print("Non-IPv4 route") continue print("%15s/%-2d via %-15s" % (inet_ntoa(dst), prefix, inet_ntoa(nh))) snimpy-0.8.13/examples/get-serial.py0000644000076400001440000000163013012531051017773 0ustar bernatusers00000000000000#!/usr/bin/snimpy """ Get serial number of a given equipment using ENTITY-MIB """ from __future__ import print_function import sys load("ENTITY-MIB") host=sys.argv[1] s = M(host=host, community=sys.argv[2]) # Locate parent of all other elements print("[-] %s: Search for parent element" % host) parent = None for i in s.entPhysicalContainedIn: if s.entPhysicalContainedIn[i] == 0: parent = i break if parent is None: print("[!] %s: Unable to find parent" % host) sys.exit(1) print("[+] %s: %s" % (host, s.entPhysicalDescr[parent])) print("[+] %s: HW %s, FW %s, SW %s" % (host, s.entPhysicalHardwareRev[parent], s.entPhysicalFirmwareRev[parent], s.entPhysicalSoftwareRev[parent])) print("[+] %s: SN %s" % (host, s.entPhysicalSerialNum[parent])) snimpy-0.8.13/examples/list-interfaces.py0000644000076400001440000000023113012531051021027 0ustar bernatusers00000000000000#!/usr/bin/snimpy from __future__ import print_function load("IF-MIB") m=M() for i in m.ifDescr: print("Interface %3d: %s" % (i, m.ifDescr[i])) snimpy-0.8.13/snimpy/0000755000076400001440000000000013360401663015101 5ustar bernatusers00000000000000snimpy-0.8.13/snimpy/config.py0000644000076400001440000000335013357726113016727 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # import os.path import imp class Conf: prompt = "\033[1m[snimpy]>\033[0m " histfile = "~/.snimpy_history" # Not used with IPython userconf = "~/.snimpy.conf" ipython = True ipythonprofile = None # Set for example to "snimpy" mibs = [] def load(self, userconf=None): if userconf is None: userconf = self.userconf try: conffile = open(os.path.expanduser(userconf)) except (OSError, IOError): pass else: try: confuser = imp.load_module("confuser", conffile, os.path.expanduser(userconf), ("conf", 'r', imp.PY_SOURCE)) for k in confuser.__dict__: if not k.startswith("__"): setattr(self, k, confuser.__dict__[k]) finally: conffile.close() return self snimpy-0.8.13/snimpy/_version.py0000644000076400001440000000020513360401662017273 0ustar bernatusers00000000000000 # This file is automatically generated by setup.py. __version__ = '0.8.13' __sha__ = 'gf167b07635ef' __revision__ = 'gf167b07635ef' snimpy-0.8.13/snimpy/basictypes.py0000644000076400001440000007353213357726113017641 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # """ This module is aimed at providing Pythonic representation of various SNMP types. Each SMIv2 type is mapped to a corresponding class which tries to mimic a basic type from Python. For example, display strings are like Python string while SMIv2 integers are just like Python integers. This module is some kind of a hack and its use outside of *Snimpy* seems convoluted. """ import sys import struct import socket import re from datetime import timedelta from pysnmp.proto import rfc1902 from snimpy import mib PYTHON3 = sys.version_info >= (3, 0) if PYTHON3: def ord2(x): return x def chr2(x): return bytes([x & 0xff]) unicode = str long = int else: ord2 = ord def chr2(x): return chr(x & 0xff) def ordering_with_cmp(cls): ops = {'__lt__': lambda self, other: self.__cmp__(other) < 0, '__gt__': lambda self, other: self.__cmp__(other) > 0, '__le__': lambda self, other: self.__cmp__(other) <= 0, '__ge__': lambda self, other: self.__cmp__(other) >= 0, '__eq__': lambda self, other: self.__cmp__(other) == 0, '__ne__': lambda self, other: self.__cmp__(other) != 0} for opname, opfunc in ops.items(): opfunc.__name__ = opname opfunc.__doc__ = getattr(int, opname).__doc__ setattr(cls, opname, opfunc) return cls class Type(object): """Base class for all types.""" def __new__(cls, entity, value, raw=True): """Create a new typed value. :param entity: A :class:`mib.Node` instance :param value: The value to set :param raw: Whetever the raw value is provided (as opposed to a user-supplied value). This parameter is important when the provided input is ambiguous, for example when it is an array of bytes. :type raw: bool :return: an instance of the new typed value """ if entity.type != cls: raise ValueError("MIB node is {0}. We are {1}".format(entity.type, cls)) if cls == OctetString and entity.fmt is not None: # Promotion of OctetString to String if we have unicode stuff if isinstance(value, (String, unicode)) or not raw: cls = String if not isinstance(value, Type): value = cls._internal(entity, value) else: value = cls._internal(entity, value._value) if issubclass(cls, unicode): self = unicode.__new__(cls, value) elif issubclass(cls, bytes): self = bytes.__new__(cls, value) elif issubclass(cls, long): self = long.__new__(cls, value) else: self = object.__new__(cls) self._value = value self.entity = entity if cls == OctetString and entity.fmt is not None: # A display-hint propose to use only ascii and UTF-8 # chars. We promote an OCTET-STRING to a DisplayString if # we have a format. This means we won't be able to access # individual bytes in this format, only the full displayed # version. value = String._internal(entity, self) self = unicode.__new__(String, value) self._value = value self.entity = entity if isinstance(self, String): # Ensure that strings follow their format, if it is applied. # This is safer and simpler than toOid, as it does not do # additional validation. self._toBytes() return self @classmethod def _internal(cls, entity, value): """Get internal value for a given value.""" raise NotImplementedError # pragma: no cover def pack(self): """Prepare the instance to be sent on the wire.""" raise NotImplementedError # pragma: no cover def toOid(self, implied=False): """Convert to an OID. If this function is implemented, then class function :meth:`fromOid` should also be implemented as the "invert" function of this one. This function only works if the entity is used as an index! Otherwise, it should raises NotImplementedError. :return: An OID that can be used as index """ raise NotImplementedError # pragma: no cover @classmethod def fromOid(cls, entity, oid, implied=False): """Create instance from an OID. This is the sister function of :meth:`toOid`. :param oid: The OID to use to create an instance :param entity: The MIB entity we want to instantiate :return: A couple `(l, v)` with `l` the number of suboids needed to create the instance and `v` the instance created from the OID """ raise NotImplementedError # pragma: no cover @classmethod def _fixedLen(cls, entity): """Determine if the given entity is fixed-len This function is a helper that is used for String and Oid. When converting a variable-length type to an OID, we need to prefix it by its len or not depending of what the MIB say. Node that the type can be used in an index with IMPLIED keyword. In that case, even when this function returns False, the OID will not be prefixed by its length. :param entity: entity to check :return: `True` if it is fixed-len, `False` otherwise """ if entity.ranges and not isinstance(entity.ranges, (tuple, list)): # Fixed length return True else: # Variable length return False def __str__(self): return str(self._value) def __repr__(self): return '<{0}: {1}>'.format(self.__class__.__name__, str(self)) @ordering_with_cmp class IpAddress(Type): """Class representing an IP address/""" @classmethod def _internal(cls, entity, value): if isinstance(value, (list, tuple)): value = ".".join([str(a) for a in value]) elif isinstance(value, bytes) and len(value) == 4: value = socket.inet_ntoa(value) try: value = socket.inet_ntoa(socket.inet_aton(value)) except socket.error: raise ValueError("{0!r} is not a valid IP".format(value)) return [int(a) for a in value.split(".")] def pack(self): return ( rfc1902.IpAddress( str(".".join(["{0:d}".format(x) for x in self._value]))) ) def toOid(self, implied=False): return tuple(self._value) @classmethod def fromOid(cls, entity, oid, implied=False): if len(oid) < 4: raise ValueError( "{0!r} is too short for an IP address".format(oid)) return (4, cls(entity, oid[:4])) def __str__(self): return ".".join([str(a) for a in self._value]) def __cmp__(self, other): if not isinstance(other, IpAddress): try: other = IpAddress(self.entity, other) except Exception: raise NotImplementedError # pragma: no cover if self._value == other._value: return 0 if self._value < other._value: return -1 return 1 def __getitem__(self, nb): return self._value[nb] class StringOrOctetString(Type): def toOid(self, implied=False): # To convert properly to OID, we need to know if it is a # fixed-len string, an implied string or a variable-len # string. b = self._toBytes() if implied or self._fixedLen(self.entity): return tuple(ord2(a) for a in b) else: return tuple([len(b)] + [ord2(a) for a in b]) def _toBytes(self): raise NotImplementedError @classmethod def fromOid(cls, entity, oid, implied=False): if implied: # Eat everything return (len(oid), cls(entity, b"".join([chr2(x) for x in oid]))) if cls._fixedLen(entity): length = entity.ranges if len(oid) < length: raise ValueError( "{0} is too short for wanted fixed " "string (need at least {1:d})".format(oid, length)) return (length, cls(entity, b"".join([chr2(x) for x in oid[:length]]))) # This is var-len if not oid: raise ValueError("empty OID while waiting for var-len string") length = oid[0] if len(oid) < length + 1: raise ValueError( "{0} is too short for variable-len " "string (need at least {1:d})".format(oid, length)) return ( (length + 1, cls(entity, b"".join([chr2(x) for x in oid[1:(length + 1)]])))) def pack(self): return rfc1902.OctetString(self._toBytes()) class OctetString(StringOrOctetString, bytes): """Class for a generic octet string. This class should be compared to :class:`String` which is used to represent a display string. This class is usually used to store raw bytes, like a bitmask of VLANs. """ @classmethod def _internal(cls, entity, value): # Internally, we are using bytes if isinstance(value, bytes): return value if isinstance(value, unicode): return value.encode("ascii") return bytes(value) def _toBytes(self): return self._value def __ior__(self, value): nvalue = [ord2(u) for u in self._value] if not isinstance(value, (tuple, list)): value = [value] for v in value: if not isinstance(v, (int, long)): raise NotImplementedError( "on string, bit-operation are limited to integers") if len(nvalue) < (v >> 3) + 1: nvalue.extend([0] * ((v >> 3) + 1 - len(self._value))) nvalue[v >> 3] |= 1 << (7 - v % 8) return self.__class__(self.entity, b"".join([chr2(i) for i in nvalue])) def __isub__(self, value): nvalue = [ord2(u) for u in self._value] if not isinstance(value, (tuple, list)): value = [value] for v in value: if not isinstance(v, int) and not isinstance(v, long): raise NotImplementedError( "on string, bit-operation are limited to integers") if len(nvalue) < (v >> 3) + 1: continue nvalue[v >> 3] &= ~(1 << (7 - v % 8)) return self.__class__(self.entity, b"".join([chr2(i) for i in nvalue])) return self def __and__(self, value): nvalue = [ord2(u) for u in self._value] if not isinstance(value, (tuple, list)): value = [value] for v in value: if not isinstance(v, (int, long)): raise NotImplementedError( "on string, bit-operation are limited to integers") if len(nvalue) < (v >> 3) + 1: return False if not(nvalue[v >> 3] & (1 << (7 - v % 8))): return False return True class String(StringOrOctetString, unicode): """Class for a display string. Such a string is an unicode string and it is therefore expected that only printable characters are used. This is usually the case if the corresponding MIB node comes with a format string. With such an instance, the user is expected to be able to provide a formatted. For example, a MAC address could be written `00:11:22:33:44:55`. """ @classmethod def _parseOctetFormat(cls, fmt, j): # repeater if fmt[j] == "*": dorepeat = True j += 1 else: dorepeat = False # length length = "" while fmt[j].isdigit(): length += fmt[j] j += 1 length = int(length) # format format = fmt[j] j += 1 # seperator if j < len(fmt) and \ fmt[j] != "*" and not fmt[j].isdigit(): sep = fmt[j] j += 1 else: sep = "" # terminator if j < len(fmt) and \ fmt[j] != "*" and not fmt[j].isdigit(): term = fmt[j] j += 1 else: term = "" return (j, dorepeat, length, format, sep, term) @classmethod def _fromBytes(cls, value, fmt): i = 0 # Position in value j = 0 # Position in fmt result = "" term = None sep = None while i < len(value): if j < len(fmt): j, dorepeat, length, format, sep, term = cls._parseOctetFormat( fmt, j) # building if dorepeat: repeat = ord2(value[i]) i += 1 else: repeat = 1 for r in range(repeat): bb = value[i:i + length] i += length if format in ['o', 'x', 'd']: if length > 4: raise ValueError( "don't know how to handle integers " "more than 4 bytes long") bb = b"\x00" * (4 - length) + bb number = struct.unpack(b"!l", bb)[0] if format == "o": # In Python2, oct() is 01242, while it is 0o1242 in # Python3 result += "".join(oct(number).partition("o")[0:3:2]) elif format == "x": result += hex(number)[2:] else: # format == "d": result += str(number) elif format == "a": result += bb.decode("ascii") elif format == "t": result += bb.decode("utf-8") else: raise ValueError("{0!r} cannot be represented with " "the given display string ({1})".format( bb, fmt)) result += sep if sep and term: result = result[:-1] result += term if term or sep: result = result[:-1] return result def _toBytes(self): # We need to reverse what was done by `_fromBytes`. This is # not an exact science. In most case, this is easy because a # separator is used but sometimes, this is not. We do some # black magic that will fail. i = 0 j = 0 fmt = self.entity.fmt bb = b"" while i < len(self._value): if j < len(fmt): parsed = self._parseOctetFormat(fmt, j) j, dorepeat, length, format, sep, term = parsed if format == "o": fmatch = "(?P[0-7]{{1,{0}}})".format( int(length * 2.66667) + 1) elif format == "x": fmatch = "(?P[0-9A-Fa-f]{{1,{0}}})".format(length * 2) elif format == "d": fmatch = "(?P[0-9]{{1,{0}}})".format( int(length * 2.4083) + 1) elif format == "a": fmatch = "(?P(?:.|\n){{1,{0}}})".format(length) elif format == "t": fmatch = "(?P(?:.|\n){{1,{0}}})".format(length) else: raise ValueError("{0!r} cannot be parsed due to an " "incorrect format ({1})".format( self._value, fmt)) repeats = [] while True: mo = re.match(fmatch, self._value[i:]) if not mo: raise ValueError("{0!r} cannot be parsed because it " "does not match format {1} at " "index {2}".format(self._value, fmt, i)) if format in ["o", "x", "d"]: if format == "o": r = int(mo.group("o"), 8) elif format == "x": r = int(mo.group("x"), 16) else: r = int(mo.group("d")) result = struct.pack(b"!l", r)[-length:] else: result = mo.group(1).encode("utf-8") i += len(mo.group(1)) if dorepeat: repeats.append(result) if i < len(self._value): # Approximate... if sep and self._value[i] == sep: i += 1 elif term and self._value[i] == term: i += 1 break else: break else: break if dorepeat: bb += chr2(len(repeats)) bb += b"".join(repeats) else: bb += result if i < len(self._value) and (sep and self._value[i] == sep or term and self._value[i] == term): i += 1 return bb @classmethod def _internal(cls, entity, value): # Internally, we use the displayed string. We have a special # case if the value is an OctetString to do the conversion. if isinstance(value, OctetString): return cls._fromBytes(value._value, entity.fmt) if PYTHON3 and isinstance(value, bytes): return value.decode("utf-8") return unicode(value) def __str__(self): return self._value class Integer(Type, long): """Class for any integer.""" @classmethod def _internal(cls, entity, value): return long(value) def pack(self): if self._value >= (1 << 64): raise OverflowError("too large to be packed") if self._value >= (1 << 32): return rfc1902.Counter64(self._value) if self._value >= 0: return rfc1902.Integer(self._value) if self._value >= -(1 << 31): return rfc1902.Integer(self._value) raise OverflowError("too small to be packed") def toOid(self, implied=False): return (self._value,) @classmethod def fromOid(cls, entity, oid, implied=False): if len(oid) < 1: raise ValueError("{0} is too short for an integer".format(oid)) return (1, cls(entity, oid[0])) def __str__(self): if self.entity.fmt: if self.entity.fmt[0] == "x": return hex(self._value) if self.entity.fmt[0] == "o": return oct(self._value) if self.entity.fmt[0] == "b": if self._value == 0: return "0" if self._value > 0: v = self._value r = "" while v > 0: r = str(v % 2) + r v = v >> 1 return r elif self.entity.fmt[0] == "d" and \ len(self.entity.fmt) > 2 and \ self.entity.fmt[1] == "-": dec = int(self.entity.fmt[2:]) result = str(self._value) if len(result) < dec + 1: result = "0" * (dec + 1 - len(result)) + result return "{0}.{1}".format(result[:-2], result[-2:]) return str(self._value) class Unsigned32(Integer): """Class to represent an unsigned 32bits integer.""" def pack(self): if self._value >= (1 << 32): raise OverflowError("too large to be packed") if self._value < 0: raise OverflowError("too small to be packed") return rfc1902.Unsigned32(self._value) class Unsigned64(Integer): """Class to represent an unsigned 64bits integer.""" def pack(self): if self._value >= (1 << 64): raise OverflowError("too large to be packed") if self._value < 0: raise OverflowError("too small to be packed") return rfc1902.Counter64(self._value) class Enum(Integer): """Class for an enumeration. An enumaration is an integer but labels are attached to some values for a more user-friendly display.""" @classmethod def _internal(cls, entity, value): if value in entity.enum: return value for (k, v) in entity.enum.items(): if (v == value): return k try: return long(value) except Exception: raise ValueError("{0!r} is not a valid " "value for {1}".format(value, entity)) def pack(self): return rfc1902.Integer(self._value) @classmethod def fromOid(cls, entity, oid, implied=False): if len(oid) < 1: raise ValueError( "{0!r} is too short for an enumeration".format(oid)) return (1, cls(entity, oid[0])) def __eq__(self, other): if not isinstance(other, self.__class__): try: other = self.__class__(self.entity, other) except Exception: raise NotImplementedError # pragma: no cover return self._value == other._value def __ne__(self, other): return not(self.__eq__(other)) def __str__(self): if self._value in self.entity.enum: return ( "{0}({1:d})".format(self.entity.enum[self._value], self._value) ) else: return str(self._value) @ordering_with_cmp class Oid(Type): """Class to represent and OID.""" @classmethod def _internal(cls, entity, value): if isinstance(value, (list, tuple)): return tuple([int(v) for v in value]) elif isinstance(value, str): return tuple([ord2(i) for i in value.split(".") if i]) elif isinstance(value, mib.Node): return tuple(value.oid) else: raise TypeError( "don't know how to convert {0!r} to OID".format(value)) def pack(self): return rfc1902.univ.ObjectIdentifier(self._value) def toOid(self, implied=False): if implied or self._fixedLen(self.entity): return self._value else: return tuple([len(self._value)] + list(self._value)) @classmethod def fromOid(cls, entity, oid, implied=False): if cls._fixedLen(entity): # A fixed OID? We don't like this. Provide a real example. raise ValueError( "{0!r} seems to be a fixed-len OID index. Odd.".format(entity)) if not implied: # This index is not implied. We need the len if len(oid) < 1: raise ValueError( "{0!r} is too short for a not " "implied index".format(entity)) length = oid[0] if len(oid) < length + 1: raise ValueError( "{0!r} has an incorrect size " "(needs at least {1:d})".format(oid, length)) return (length + 1, cls(entity, oid[1:(length + 1)])) else: # This index is implied. Eat everything return (len(oid), cls(entity, oid)) def __str__(self): return ".".join([str(x) for x in self._value]) def __cmp__(self, other): if not isinstance(other, Oid): other = Oid(self.entity, other) if tuple(self._value) == tuple(other._value): return 0 if self._value > other._value: return 1 return -1 def __getitem__(self, index): return self._value[index] def __contains__(self, item): """Test if item is a sub-oid of this OID""" if not isinstance(item, Oid): item = Oid(self.entity, item) return tuple(item._value[:len(self._value)]) == \ tuple(self._value[:len(self._value)]) class Boolean(Enum): """Class for a boolean.""" @classmethod def _internal(cls, entity, value): if isinstance(value, bool): if value: return Enum._internal(entity, "true") else: return Enum._internal(entity, "false") else: return Enum._internal(entity, value) def __nonzero__(self): if self._value == 1: return True else: return False def __bool__(self): return self.__nonzero__() @ordering_with_cmp class Timeticks(Type): """Class for timeticks.""" @classmethod def _internal(cls, entity, value): if isinstance(value, (int, long)): # Value in centiseconds return timedelta(0, value / 100.) elif isinstance(value, timedelta): return value else: raise TypeError( "dunno how to handle {0!r} ({1})".format(value, type(value))) def __int__(self): return self._value.days * 3600 * 24 * 100 + \ self._value.seconds * 100 + \ self._value.microseconds // 10000 def toOid(self, implied=False): return (int(self),) @classmethod def fromOid(cls, entity, oid, implied=False): if len(oid) < 1: raise ValueError("{0!r} is too short for a timetick".format(oid)) return (1, cls(entity, oid[0])) def pack(self): return rfc1902.TimeTicks(int(self)) def __str__(self): return str(self._value) def __cmp__(self, other): if isinstance(other, Timeticks): other = other._value elif isinstance(other, (int, long)): other = timedelta(0, other / 100.) elif not isinstance(other, timedelta): raise NotImplementedError( "only compare to int or " "timedelta, not {0}".format(type(other))) if self._value == other: return 0 if self._value < other: return -1 return 1 class Bits(Type): """Class for bits.""" @classmethod def _internal(cls, entity, value): bits = set() tryalternate = False if isinstance(value, bytes): for i, x in enumerate(value): if ord2(x) == 0: continue for j in range(8): if ord2(x) & (1 << (7 - j)): k = (i * 8) + j if k not in entity.enum: tryalternate = True break bits.add(k) if tryalternate: break if not tryalternate: return bits else: bits = set() elif not isinstance(value, (tuple, list, set, frozenset)): value = set([value]) for v in value: found = False if v in entity.enum: bits.add(v) found = True else: for (k, t) in entity.enum.items(): if (t == v): bits.add(k) found = True break if not found: raise ValueError("{0!r} is not a valid bit value".format(v)) return bits def pack(self): if self._value: string = [0] * ((max(self._value) // 8) + 1) else: string = [] for b in self._value: string[b // 8] |= 1 << (7 - b % 8) return rfc1902.Bits(b"".join([chr2(x) for x in string])) def __eq__(self, other): if isinstance(other, str): other = [other] if not isinstance(other, Bits): other = Bits(self.entity, other) return self._value == other._value def __ne__(self, other): return not self.__eq__(other) def __str__(self): result = [] for b in sorted(self._value): result.append("{0}({1:d})".format(self.entity.enum[b], b)) return ", ".join(result) def __and__(self, other): if isinstance(other, str): other = [other] if not isinstance(other, Bits): other = Bits(self.entity, other) return len(self._value & other._value) > 0 def __ior__(self, other): if isinstance(other, str): other = [other] if not isinstance(other, Bits): other = Bits(self.entity, other) self._value |= other._value return self def __isub__(self, other): if isinstance(other, str): other = [other] if not isinstance(other, Bits): other = Bits(self.entity, other) self._value -= other._value return self def build(mibname, node, value): """Build a new basic type with the given value. :param mibname: The MIB to use to locate the entity. :param node: The node that will be attached to this type. :param value: The initial value to set for the type. :return: A :class:`Type` instance """ m = mib.get(mibname, node) t = m.type(m, value) return t snimpy-0.8.13/snimpy/manager.py0000644000076400001440000005053613360042242017070 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # """This module is the high-level interface to *Snimpy*. It exposes :class:`Manager` class to instantiate a new manager (which is an SNMP client). This is the preferred interface for *Snimpy*. Here is a simple example of use of this module:: >>> load("IF-MIB") >>> m = Manager("localhost") >>> m.ifDescr[1] """ import inspect from time import time from collections import MutableMapping, Container, Iterable, Sized from snimpy import snmp, mib, basictypes class DelegatedSession(object): """General class for SNMP session for delegation""" def __init__(self, session): self._session = session def __getattr__(self, attr): return getattr(self._session, attr) def __setattribute__(self, attr, value): return setattr(self._session, attr, value) class DelayedSetSession(DelegatedSession): """SNMP session that is able to delay SET requests. This is an adapter. The constructor takes the original (not delayed) session. """ def __init__(self, session): DelegatedSession.__init__(self, session) self.setters = [] def set(self, *args): self.setters.extend(args) def commit(self): if self.setters: self._session.set(*self.setters) class NoneSession(DelegatedSession): """SNMP session that will return None on unsucessful GET requests. In a normal session, a GET request returning `No such instance` error will trigger an exception. This session will catch such an error and return None instead. """ def get(self, *args): try: return self._session.get(*args) except (snmp.SNMPNoSuchName, snmp.SNMPNoSuchObject, snmp.SNMPNoSuchInstance): if len(args) > 1: # We can't handle this case yet because we don't know # which value is unavailable. raise return ((args[0], None),) class CachedSession(DelegatedSession): """SNMP session using a cache. This is an adapter. The constructor takes the original session. """ def __init__(self, session, timeout=5): DelegatedSession.__init__(self, session) self.cache = {} # contains (operation, oid) -> [time, result] entries self.timeout = timeout self.count = 0 def getorwalk(self, op, *args): self.count += 1 if (op, args) in self.cache: t, v = self.cache[op, args] if time() - t < self.timeout: return v value = getattr(self._session, op)(*args) self.cache[op, args] = [time(), value] if op == "walkmore": # also cache all the get requests we got for free for oid, get_value in value: self.count += 1 self.cache["get", (oid, )] = [time(), ((oid, get_value), )] self.flush() return value def get(self, *args): return self.getorwalk("get", *args) def walk(self, *args): assert(len(args) == 1) # we should ony walk one oid at a time return self.getorwalk("walkmore", *args) def flush(self): keys = list(self.cache.keys()) for k in keys: if time() - self.cache[k][0] > self.timeout: del self.cache[k] self.count = 0 def MibRestrictedManager(original, mibs): """Copy an existing manager but restrict its view to the given set of MIBs. """ clone = Manager(**original._constructor_args) clone._loaded = mibs return clone class Manager(object): """SNMP manager. An instance of this class will represent an SNMP manager (client). When a MIB is loaded with :func:`load`, scalars and row names from it will be made available as an instance attribute. For a scalar, reading the corresponding attribute will get its value while setting it will allow to modify it: >>> load("SNMPv2-MIB") >>> m = Manager("localhost", "private") >>> m.sysContact >>> m.sysContact = "Brian Jones" >>> m.sysContact For a row name, the provided interface is like a Python dictionary. Requesting an item using its index will retrieve the value from the agent (the server):: >>> load("IF-MIB") >>> m = Manager("localhost", "private") >>> m.ifDescr[1] >>> m.ifName[1] = "Loopback interface" Also, it is possible to iterate on a row name to get all available values for index:: >>> load("IF-MIB") >>> m = Manager("localhost", "private") >>> for idx in m.ifDescr: ... print(m.ifDescr[idx]) You can get a slice of index values from a table by iterating on a row name subscripted by a partial index:: >>> load("IF-MIB") >>> m = Manager("localhost", "private") >>> for idx in m.ipNetToMediaPhysAddress[1]: ... print(idx) (, ) You can use multivalue indexes in two ways: using Pythonic multi-dimensional dict syntax, or by providing a tuple containing index values:: >>> load("IF-MIB") >>> m = Manager("localhost", "private") >>> m.ipNetToMediaPhysAddress[1]['127.0.0.1'] >>> m.ipNetToMediaPhysAddress[1, '127.0.0.1'] A context manager is also provided. Any modification issued inside the context will be delayed until the end of the context and then grouped into a single SNMP PDU to be executed atomically:: >>> load("IF-MIB") >>> m = Manager("localhost", "private") >>> with m: ... m.ifName[1] = "Loopback interface" ... m.ifName[2] = "First interface" Any error will be turned into an exception:: >>> load("IF-MIB") >>> m = Manager("localhost", "private") >>> m.ifDescr[999] Traceback (most recent call last): ... SNMPNoSuchName: There is no such variable name in this MIB. """ # do we want this object to be populated with all nodes? _complete = False def __init__(self, host="localhost", community="public", version=2, cache=False, none=False, timeout=None, retries=None, loose=False, bulk=40, # SNMPv3 secname=None, authprotocol=None, authpassword=None, privprotocol=None, privpassword=None, contextname=None): """Create a new SNMP manager. Some of the parameters are explained in :meth:`snmp.Session.__init__`. :param host: The hostname or IP address of the agent to connect to. Optionally, the port can be specified separated with a double colon. :type host: str :param community: The community to transmit to the agent for authorization purpose. This parameter is ignored if the specified version is 3. :type community: str :param version: The SNMP version to use to talk with the agent. Possible values are `1`, `2` (community-based) or `3`. :type version: int :param cache: Should caching be enabled? This can be either a boolean or an integer to specify the cache timeout in seconds. If `True`, the default timeout is 5 seconds. :type cache: bool or int :param none: Should `None` be returned when the agent does not know the requested OID? If `True`, `None` will be returned when requesting an inexisting scalar or column. :type none: bool :param timeout: Use the specified value in seconds as timeout. :type timeout: int :param retries: How many times the request should be retried? :type retries: int :param loose: Enable loose typing. When type coercion fails (for example when a MIB declare an element to be an ASCII string while it is not), just return the raw result instead of an exception. This mode should be enabled with caution. Patching the MIB is a better idea. :type loose: bool :param bulk: Max-repetition to use to speed up MIB walking with `GETBULK`. Set to `0` to disable. :type bulk: int """ if host is None: host = Manager._host self._host = host self._session = snmp.Session(host, community, version, secname, authprotocol, authpassword, privprotocol, privpassword, contextname=contextname, bulk=bulk) if timeout is not None: self._session.timeout = int(timeout * 1000000) if retries is not None: self._session.retries = retries if cache: if cache is True: self._session = CachedSession(self._session) else: self._session = CachedSession(self._session, cache) if none: self._session = NoneSession(self._session) self._loose = loose self._loaded = loaded # To be able to clone, we save the arguments provided to the # constructor in a generic way frame = inspect.currentframe() args, _, _, values = inspect.getargvalues(frame) self._constructor_args = dict((a, values[a]) for a in args if a != 'self') def _locate(self, attribute): for m in self._loaded: try: a = mib.get(m, attribute) return (m, a) except mib.SMIException: pass raise AttributeError("{0} is not an attribute".format(attribute)) def __getattribute__(self, attribute): if attribute.startswith("_"): return object.__getattribute__(self, attribute) m, a = self._locate(attribute) if isinstance(a, mib.Scalar): oid, result = self._session.get(a.oid + (0,))[0] if result is not None: try: return a.type(a, result) except ValueError: if self._loose: return result raise return None elif isinstance(a, mib.Column): return ProxyColumn(self._session, a, self._loose) elif isinstance(a, mib.Table): return ProxyTable(self._session, a, self._loose) raise NotImplementedError def __setattr__(self, attribute, value): if attribute.startswith("_"): return object.__setattr__(self, attribute, value) m, a = self._locate(attribute) if not isinstance(value, basictypes.Type): value = a.type(a, value, raw=False) if isinstance(a, mib.Scalar): self._session.set(a.oid + (0,), value) return raise AttributeError("{0} is not writable".format(attribute)) def __getitem__(self, modulename): modulename = modulename.encode('ascii') for m in loaded: if modulename == m: return MibRestrictedManager(self, [m]) raise KeyError("{0} is not a loaded module".format(modulename)) def __repr__(self): return "".format(self._host) def __enter__(self): """In a context, we group all "set" into a single request""" self._osession = self._session self._session = DelayedSetSession(self._session) return self def __exit__(self, type, value, traceback): """When we exit, we should execute all "set" requests""" try: if type is None: self._session.commit() finally: self._session = self._osession del self._osession class Proxy: """A proxy for some base type, notably a column or a table.""" def __repr__(self): return "<{0} for {1}>".format(self.__class__.__name__, repr(self.proxy)[1:-1]) class ProxyIter(Proxy, Sized, Iterable, Container): """Proxy for an iterable sequence. This a proxy offering the ABC of an iterable sequence (something like a set but without set operations). This will be used by both `ProxyColumn` and `ProxyTable`. """ def _op(self, op, index, *args): if not isinstance(index, tuple): index = (index,) indextype = self.proxy.table.index if len(indextype) != len(index): raise ValueError( "{0} column uses the following " "indexes: {1!r}".format(self.proxy, indextype)) oidindex = [] for i, ind in enumerate(index): # Cast to the correct type since we need "toOid()" ind = indextype[i].type(indextype[i], ind, raw=False) implied = self.proxy.table.implied and i == len(index)-1 oidindex.extend(ind.toOid(implied)) result = getattr( self.session, op)(self.proxy.oid + tuple(oidindex), *args) if op != "set": oid, result = result[0] if result is not None: try: return self.proxy.type(self.proxy, result) except ValueError: if self._loose: return result raise return None def __contains__(self, object): try: self._op("get", object) except Exception: return False return True def __iter__(self): for k, _ in self.iteritems(): yield k def __len__(self): len(list(self.iteritems())) def iteritems(self, table_filter=None): count = 0 oid = self.proxy.oid indexes = self.proxy.table.index if table_filter is not None: if len(table_filter) >= len(indexes): raise ValueError("Table filter has too many elements") oid_suffix = [] # Convert filter elements to correct types for i, part in enumerate(table_filter): part = indexes[i].type(indexes[i], part, raw=False) # implied = False: # index never includes last element # (see 'len(table_filter) >= len(indexes)') oid_suffix.extend(part.toOid(implied=False)) oid += tuple(oid_suffix) walk_oid = oid for noid, result in self.session.walk(oid): if noid <= oid: noid = None break oid = noid if not((len(oid) >= len(walk_oid) and oid[:len(walk_oid)] == walk_oid[:len(walk_oid)])): noid = None break # oid should be turned into index index = tuple(oid[len(self.proxy.oid):]) target = [] for i, x in enumerate(indexes): implied = self.proxy.table.implied and i == len(indexes)-1 l, o = x.type.fromOid(x, index, implied) target.append(x.type(x, o)) index = index[l:] count = count + 1 if result is not None: try: result = self.proxy.type(self.proxy, result) except ValueError: if not self._loose: raise if len(target) == 1: # Should work most of the time yield target[0], result else: yield tuple(target), result if count == 0: # We did not find any element. Is it because the column is # empty or because the column does not exist. We do a SNMP # GET to know. If we get a "No such instance" exception, # this means the column is empty. If we get "No such # object", this means the column does not exist. We cannot # make such a distinction with SNMPv1. try: self.session.get(self.proxy.oid) except snmp.SNMPNoSuchInstance: # OK, the set of result is really empty return except snmp.SNMPNoAccess: # Some implementations seem to return NoAccess (PySNMP is one) return except snmp.SNMPNoSuchName: # SNMPv1, we don't know pass except snmp.SNMPNoSuchObject: # The result is empty because the column is unknown raise class ProxyTable(ProxyIter): """Proxy for table access. We just use the first accessible index as a column. However, the mapping operations are not available. """ def __init__(self, session, table, loose): self.proxy = None for column in table.columns: if column.accessible: self.proxy = column break if self.proxy is None: raise NotImplementedError("No accessible column in the table.") self.session = session self._loose = loose class ProxyColumn(ProxyIter, MutableMapping): """Proxy for column access""" def __init__(self, session, column, loose, oid_suffix=()): self.proxy = column self.session = session self._loose = loose self._oid_suffix = oid_suffix def __getitem__(self, index): # If supplied index is partial we return new ProxyColumn # with appended OID suffix idx_len = len(self.proxy.table.index) suffix_len = len(self._oid_suffix) if isinstance(index, tuple): if len(index) + suffix_len < idx_len: return self._partial(index) elif idx_len > suffix_len + 1: return self._partial((index,)) # Otherwise a read op is made if not isinstance(index, tuple): index = (index,) return self._op("get", self._oid_suffix + index) def __setitem__(self, index, value): if not isinstance(value, basictypes.Type): value = self.proxy.type(self.proxy, value, raw=False) if not isinstance(index, tuple): index = (index,) self._op("set", self._oid_suffix + index, value) def __delitem__(self, index): raise NotImplementedError("cannot suppress a column") def __contains__(self, index): if not isinstance(index, tuple): index = (index,) return ProxyIter.__contains__(self, self._oid_suffix + index) def _partial(self, index): """Create new ProxyColumn based on current one, but with appended OID suffix""" new_suffix = self._oid_suffix + index return ProxyColumn(self.session, self.proxy, self._loose, new_suffix) def iteritems(self, table_filter=None): resulting_filter = self._oid_suffix if table_filter is not None: if not isinstance(table_filter, tuple): table_filter = (table_filter,) resulting_filter += table_filter return ProxyIter.iteritems(self, resulting_filter) loaded = [] def load(mibname): """Load a MIB in memory. :param mibname: MIB name or filename :type mibname: str """ m = mib.load(mibname) if m not in loaded: loaded.append(m) if Manager._complete: for o in mib.getScalars(m) + \ mib.getColumns(m) + \ mib.getTables(m): setattr(Manager, str(o), 1) snimpy-0.8.13/snimpy/mib.py0000644000076400001440000005630613357726113016242 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # """This module is a low-level interface to manipulate and extract information from MIB files. It is a CFFI_ wrapper around libsmi_. You may find convenient to use it in other projects but the wrapper is merely here to serve *Snimpy* use and is therefore incomplete. .. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/ .. _CFFI: http://cffi.readthedocs.io/ """ try: from snimpy._smi import lib as _smi from snimpy._smi import ffi except ImportError: from snimpy.smi_build import ffi, get_lib _smi = get_lib() class SMIException(Exception): """SMI related exception. Any exception thrown in this module is inherited from this one.""" class Node(object): """MIB node. An instance of this class represents a MIB node. It can be specialized by other classes, like :class:`Scalar`, :class:`Table`, :class:`Column`, :class:`Node`. """ def __init__(self, node): """Create a new MIB node. :param node: libsmi node supporting this node. """ self.node = node self._override_type = None @property def type(self): """Get the basic type associated with this node. :return: The class from :mod:`basictypes` module which can represent the node. When retrieving a valid value for this node, the returned class can be instanciated to get an appropriate representation. """ from snimpy import basictypes if self._override_type: t = self._override_type else: t = _smi.smiGetNodeType(self.node) if t == ffi.NULL: raise SMIException("unable to retrieve type of node") target = { _smi.SMI_BASETYPE_INTEGER32: basictypes.Integer, _smi.SMI_BASETYPE_INTEGER64: basictypes.Integer, _smi.SMI_BASETYPE_UNSIGNED32: {b"TimeTicks": basictypes.Timeticks, None: basictypes.Unsigned32}, _smi.SMI_BASETYPE_UNSIGNED64: basictypes.Unsigned64, _smi.SMI_BASETYPE_OCTETSTRING: {b"IpAddress": basictypes.IpAddress, None: basictypes.OctetString}, _smi.SMI_BASETYPE_OBJECTIDENTIFIER: basictypes.Oid, _smi.SMI_BASETYPE_ENUM: {b"TruthValue": basictypes.Boolean, None: basictypes.Enum}, _smi.SMI_BASETYPE_BITS: basictypes.Bits }.get(t.basetype, None) if isinstance(target, dict): tt = _smi.smiGetParentType(t) target = target.get((t.name != ffi.NULL and ffi.string(t.name)) or (tt.name != ffi.NULL and ffi.string( tt.name)) or None, target.get(None, None)) if target is None: raise SMIException("unable to retrieve type of node") return target @property def typeName(self): """Retrieves the name of the the node's current declared type (not basic type). :return: A string representing the current declared type, suitable for assignment to type.setter. """ if self._override_type: t = self._override_type else: t = _smi.smiGetNodeType(self.node) # This occurs when the type is "implied". if t.name == ffi.NULL: t = _smi.smiGetParentType(t) if t is None or t == ffi.NULL: raise SMIException("unable to retrieve the declared type " "of the node '{0}'".format(self.node.name)) return ffi.string(t.name) @typeName.setter def typeName(self, type_name): """Override the node's type to type_name, found using _getType. The new type must resolve to the same basictype. :param type_name: string name of the type. """ current_override = self._override_type declared_type = _smi.smiGetNodeType(self.node) declared_basetype = self.type new_type = _getType(type_name) if not new_type: raise SMIException("no type named {0} in any loaded module".format( type_name)) # Easiest way to find the new basetype is to set the override # and ask. self._override_type = new_type new_basetype = self.type if declared_basetype != new_basetype: self._override_type = current_override raise SMIException("override type {1} not compatible with " "basetype of {0}".format( ffi.string(declared_type.name), ffi.string(new_type.name))) @typeName.deleter def typeName(self): """Clears the type override.""" self._override_type = None @property def fmt(self): """Get node format. The node format is a string to use to display a user-friendly version of the node. This is can be used for both octet strings or integers (to make them appear as decimal numbers). :return: The node format as a string or None if there is no format available. """ if self._override_type: t = self._override_type else: t = _smi.smiGetNodeType(self.node) tt = _smi.smiGetParentType(t) f = (t != ffi.NULL and t.format != ffi.NULL and ffi.string(t.format) or tt != ffi.NULL and tt.format != ffi.NULL and ffi.string(tt.format)) or None if f is None: return None return f.decode("ascii") @property def oid(self): """Get OID for the current node. The OID can then be used to request the node from an SNMP agent. :return: OID as a tuple """ return tuple([self.node.oid[i] for i in range(self.node.oidlen)]) @property def ranges(self): """Get node ranges. An node can be restricted by a set of ranges. For example, an integer can only be provided between two values. For strings, the restriction is on the length of the string. The returned value can be `None` if no restriction on range exists for the current node, a single value if the range is fixed or a list of tuples or fixed values otherwise. :return: The valid range for this node. """ t = _smi.smiGetNodeType(self.node) if t == ffi.NULL: return None ranges = [] range = _smi.smiGetFirstRange(t) while range != ffi.NULL: m1 = self._convert(range.minValue) m2 = self._convert(range.maxValue) if m1 == m2: ranges.append(m1) else: ranges.append((m1, m2)) range = _smi.smiGetNextRange(range) if len(ranges) == 0: return None if len(ranges) == 1: return ranges[0] return ranges @property def enum(self): """Get possible enum values. When the node can only take a discrete number of values, those values are defined in the MIB and can be retrieved through this property. :return: The dictionary of possible values keyed by the integer value. """ t = _smi.smiGetNodeType(self.node) if t == ffi.NULL or t.basetype not in (_smi.SMI_BASETYPE_ENUM, _smi.SMI_BASETYPE_BITS): return None result = {} element = _smi.smiGetFirstNamedNumber(t) while element != ffi.NULL: result[self._convert(element.value)] = ffi.string( element.name).decode("ascii") element = _smi.smiGetNextNamedNumber(element) return result @property def accessible(self): return (self.node.access not in (_smi.SMI_ACCESS_NOT_IMPLEMENTED, _smi.SMI_ACCESS_NOT_ACCESSIBLE)) def __str__(self): return ffi.string(self.node.name).decode("ascii") def __repr__(self): r = _smi.smiRenderNode(self.node, _smi.SMI_RENDER_ALL) if r == ffi.NULL: return "".format( self.__class__.__name__, hex(id(self))) r = ffi.gc(r, _smi.free) module = _smi.smiGetNodeModule(self.node) if module == ffi.NULL: raise SMIException("unable to get module for {0}".format( self.node.name)) return "<{0} {1} from '{2}'>".format(self.__class__.__name__, ffi.string(r), ffi.string(module.name)) def _convert(self, value): attr = {_smi.SMI_BASETYPE_INTEGER32: "integer32", _smi.SMI_BASETYPE_UNSIGNED32: "unsigned32", _smi.SMI_BASETYPE_INTEGER64: "integer64", _smi.SMI_BASETYPE_UNSIGNED64: "unsigned64"}.get(value.basetype, None) if attr is None: raise SMIException("unexpected type found in range") return getattr(value.value, attr) class Scalar(Node): """MIB scalar node. This class represents a scalar value in the MIB. A scalar value is a value not contained in a table. """ class Table(Node): """MIB table node. This class represents a table. A table is an ordered collection of objects consisting of zero or more rows. Each object in the table is identified using an index. An index can be a single value or a list of values. """ @property def columns(self): """Get table columns. The columns are the different kind of objects that can be retrieved in a table. :return: list of table columns (:class:`Column` instances) """ child = _smi.smiGetFirstChildNode(self.node) if child == ffi.NULL: return [] if child.nodekind != _smi.SMI_NODEKIND_ROW: raise SMIException("child {0} of {1} is not a row".format( ffi.string(child.name), ffi.string(self.node.name))) columns = [] child = _smi.smiGetFirstChildNode(child) while child != ffi.NULL: if child.nodekind != _smi.SMI_NODEKIND_COLUMN: raise SMIException("child {0} of {1} is not a column".format( ffi.string(child.name), ffi.string(self.node.name))) columns.append(Column(child)) child = _smi.smiGetNextChildNode(child) return columns @property def _row(self): """Get the table row. :return: row object (as an opaque object) """ child = _smi.smiGetFirstChildNode(self.node) if child != ffi.NULL and child.indexkind == _smi.SMI_INDEX_AUGMENT: child = _smi.smiGetRelatedNode(child) if child == ffi.NULL: raise SMIException("AUGMENT index for {0} but " "unable to retrieve it".format( ffi.string(self.node.name))) if child == ffi.NULL: raise SMIException("{0} does not have a row".format( ffi.string(self.node.name))) if child.nodekind != _smi.SMI_NODEKIND_ROW: raise SMIException("child {0} of {1} is not a row".format( ffi.string(child.name), ffi.string(self.node.name))) if child.indexkind != _smi.SMI_INDEX_INDEX: raise SMIException("child {0} of {1} has an unhandled " "kind of index".format( ffi.string(child.name), ffi.string(self.node.name))) return child @property def implied(self): """Is the last index implied? An implied index is an index whose size is not fixed but who is not prefixed by its size because this is the last index of a table. :return: `True` if and only if the last index is implied. """ child = self._row if child.implied: return True return False @property def index(self): """Get indexes for a table. The indexes are used to locate a precise row in a table. They are a subset of the table columns. :return: The list of indexes (as :class:`Column` instances) of the table. """ child = self._row lindex = [] element = _smi.smiGetFirstElement(child) while element != ffi.NULL: nelement = _smi.smiGetElementNode(element) if nelement == ffi.NULL: raise SMIException("cannot get index " "associated with {0}".format( ffi.string(self.node.name))) if nelement.nodekind != _smi.SMI_NODEKIND_COLUMN: raise SMIException("index {0} for {1} is " "not a column".format( ffi.string(nelement.name), ffi.string(self.node.name))) lindex.append(Column(nelement)) element = _smi.smiGetNextElement(element) return lindex class Column(Node): """MIB column node. This class represent a column of a table.""" @property def table(self): """Get table associated with this column. :return: The :class:`Table` instance associated to this column. """ parent = _smi.smiGetParentNode(self.node) if parent == ffi.NULL: raise SMIException("unable to get parent of {0}".format( ffi.string(self.node.name))) if parent.nodekind != _smi.SMI_NODEKIND_ROW: raise SMIException("parent {0} of {1} is not a row".format( ffi.string(parent.name), ffi.string(self.node.name))) parent = _smi.smiGetParentNode(parent) if parent == ffi.NULL: raise SMIException("unable to get parent of {0}".format( ffi.string(self.node.name))) if parent.nodekind != _smi.SMI_NODEKIND_TABLE: raise SMIException("parent {0} of {1} is not a table".format( ffi.string(parent.name), ffi.string(self.node.name))) t = Table(parent) return t class Notification(Node): """MIB notification node. This class represent a notification.""" @property def objects(self): """Get objects for a notification. :return: The list of objects (as :class:`Column`, :class:`Node` or :class:`Scalar` instances) of the notification. """ child = self.node lindex = [] element = _smi.smiGetFirstElement(child) while element != ffi.NULL: nelement = _smi.smiGetElementNode(element) if nelement == ffi.NULL: raise SMIException("cannot get object " "associated with {0}".format( ffi.string(self.node.name))) if nelement.nodekind == _smi.SMI_NODEKIND_COLUMN: lindex.append(Column(nelement)) elif nelement.nodekind == _smi.SMI_NODEKIND_NODE: lindex.append(Node(nelement)) elif nelement.nodekind == _smi.SMI_NODEKIND_SCALAR: lindex.append(Scalar(nelement)) else: raise SMIException("object {0} for {1} is " "not a node".format( ffi.string(nelement.name), ffi.string(self.node.name))) element = _smi.smiGetNextElement(element) return lindex _lastError = None @ffi.callback("void(char *, int, int, char *, char*)") def _logError(path, line, severity, msg, tag): global _lastError if path != ffi.NULL and msg != ffi.NULL: _lastError = "{0}:{1}: {2}".format(ffi.string(path), line, ffi.string(msg)) else: _lastError = None def reset(): """Reset libsmi to its initial state.""" _smi.smiExit() if _smi.smiInit(b"snimpy") < 0: raise SMIException("unable to init libsmi") _smi.smiSetErrorLevel(1) _smi.smiSetErrorHandler(_logError) try: _smi.smiSetFlags(_smi.SMI_FLAG_ERRORS | _smi.SMI_FLAG_RECURSIVE) except TypeError: pass # We are being mocked def path(path=None): """Set or get a search path to libsmi. When no path is provided, return the current path, unmodified. Otherwise, set the path to the specified value. :param path: The string to be used to change the search path or `None` """ if path is None: # Get the path path = _smi.smiGetPath() if path == ffi.NULL: raise SMIException("unable to get current libsmi path") path = ffi.gc(path, _smi.free) result = ffi.string(path) return result.decode("utf8") # Set the path if not isinstance(path, bytes): path = path.encode("utf8") if _smi.smiSetPath(path) < 0: raise SMIException("unable to set the path {0}".format(path)) def _get_module(name): """Get the SMI module from its name. :param name: The name of the module :return: The SMI module or `None` if not found (not loaded) """ if not isinstance(name, bytes): name = name.encode("ascii") m = _smi.smiGetModule(name) if m == ffi.NULL: return None if m.conformance and m.conformance <= 1: return None return m def _kind2object(kind): return { _smi.SMI_NODEKIND_NODE: Node, _smi.SMI_NODEKIND_SCALAR: Scalar, _smi.SMI_NODEKIND_TABLE: Table, _smi.SMI_NODEKIND_NOTIFICATION: Notification, _smi.SMI_NODEKIND_COLUMN: Column }.get(kind, Node) def get(mib, name): """Get a node by its name. :param mib: The MIB name to query :param name: The object name to get from the MIB :return: the requested MIB node (:class:`Node`) """ if not isinstance(mib, bytes): mib = mib.encode("ascii") module = _get_module(mib) if module is None: raise SMIException("no module named {0}".format(mib)) node = _smi.smiGetNode(module, name.encode("ascii")) if node == ffi.NULL: raise SMIException("in {0}, no node named {1}".format( mib, name)) pnode = _kind2object(node.nodekind) return pnode(node) def getByOid(oid): """Get a node by its OID. :param oid: The OID as a tuple :return: The requested MIB node (:class:`Node`) """ node = _smi.smiGetNodeByOID(len(oid), oid) if node == ffi.NULL: raise SMIException("no node for {0}".format( ".".join([str(o) for o in oid]))) pnode = _kind2object(node.nodekind) return pnode(node) def _getType(type_name): """Searches for a smi type through all loaded modules. :param type_name: The name of the type to search for. :return: The requested type (:class:`smi.SmiType`), if found, or None. """ if not isinstance(type_name, bytes): type_name = type_name.encode("ascii") for module in _loadedModules(): new_type = _smi.smiGetType(module, type_name) if new_type != ffi.NULL: return new_type return None def _get_kind(mib, kind): """Get nodes of a given kind from a MIB. :param mib: The MIB name to search objects for :param kind: The SMI kind of object :return: The list of matched MIB nodes for the MIB """ if not isinstance(mib, bytes): mib = mib.encode("ascii") module = _get_module(mib) if module is None: raise SMIException("no module named {0}".format(mib)) lnode = [] node = _smi.smiGetFirstNode(module, kind) while node != ffi.NULL: lnode.append(_kind2object(kind)(node)) node = _smi.smiGetNextNode(node, kind) return lnode def getNodes(mib): """Return all nodes from a given MIB. :param mib: The MIB name :return: The list of all MIB nodes for the MIB :rtype: list of :class:`Node` instances """ return _get_kind(mib, _smi.SMI_NODEKIND_NODE) def getScalars(mib): """Return all scalars from a given MIB. :param mib: The MIB name :return: The list of all scalars for the MIB :rtype: list of :class:`Scalar` instances """ return _get_kind(mib, _smi.SMI_NODEKIND_SCALAR) def getTables(mib): """Return all tables from a given MIB. :param mib: The MIB name :return: The list of all tables for the MIB :rtype: list of :class:`Table` instances """ return _get_kind(mib, _smi.SMI_NODEKIND_TABLE) def getColumns(mib): """Return all columns from a givem MIB. :param mib: The MIB name :return: The list of all columns for the MIB :rtype: list of :class:`Column` instances """ return _get_kind(mib, _smi.SMI_NODEKIND_COLUMN) def getNotifications(mib): """Return all notifications from a givem MIB. :param mib: The MIB name :return: The list of all notifications for the MIB :rtype: list of :class:`Notification` instances """ return _get_kind(mib, _smi.SMI_NODEKIND_NOTIFICATION) def load(mib): """Load a MIB into the library. :param mib: The MIB to load, either a filename or a MIB name. :return: The MIB name that has been loaded. :except SMIException: The requested MIB cannot be loaded. """ if not isinstance(mib, bytes): mib = mib.encode("ascii") modulename = _smi.smiLoadModule(mib) if modulename == ffi.NULL: raise SMIException("unable to find {0} (check the path)".format(mib)) modulename = ffi.string(modulename) if not _get_module(modulename.decode("ascii")): details = "check with smilint -s -l1" if _lastError is not None: details = "{0}: {1}".format(_lastError, details) raise SMIException( "{0} contains major SMI error ({1})".format(mib, details)) return modulename def _loadedModules(): """Generates the list of loaded modules. :yield: The :class:`smi.SmiModule` of all currently loaded modules. """ module = _smi.smiGetFirstModule() while module != ffi.NULL: yield module module = _smi.smiGetNextModule(module) def loadedMibNames(): """Generates the list of loaded MIB names. :yield: The names of all currently loaded MIBs. """ for module in _loadedModules(): yield ffi.string(module.name).decode('utf-8') reset() snimpy-0.8.13/snimpy/snmp.py0000644000076400001440000003756513357726113016456 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # """ This module is a low-level interface to build SNMP requests, send them and receive answers. It is built on top of pysnmp_ but the exposed interface is far simpler. It is also far less complete and there is an important dependency to the :mod:`basictypes` module for type coercing. .. _pysnmp: http://pysnmp.sourceforge.net/ """ import re import socket import inspect import threading from pysnmp.entity.rfc3413.oneliner import cmdgen from pysnmp.proto import rfc1902, rfc1905 from pysnmp.smi import error class SNMPException(Exception): """SNMP related base exception. All SNMP exceptions are inherited from this one. The inherited exceptions are named after the name of the corresponding SNMP error. """ class SNMPTooBig(SNMPException): pass class SNMPNoSuchName(SNMPException): pass class SNMPBadValue(SNMPException): pass class SNMPReadOnly(SNMPException): pass # Dynamically build remaining (v2) exceptions for name, obj in inspect.getmembers(error): if name.endswith("Error") and \ inspect.isclass(obj) and \ issubclass(obj, error.MibOperationError) and \ obj != error.MibOperationError: name = str("SNMP{0}".format(name[:-5])) globals()[name] = type(name, (SNMPException,), {}) del name del obj class Session(object): """SNMP session. An instance of this object will represent an SNMP session. From such an instance, one can get information from the associated agent.""" _tls = threading.local() def __init__(self, host, community="public", version=2, secname=None, authprotocol=None, authpassword=None, privprotocol=None, privpassword=None, contextname=None, bulk=40, none=False): """Create a new SNMP session. :param host: The hostname or IP address of the agent to connect to. Optionally, the port can be specified separated with a double colon. :type host: str :param community: The community to transmit to the agent for authorization purpose. This parameter is ignored if the specified version is 3. :type community: str :param version: The SNMP version to use to talk with the agent. Possible values are `1`, `2` (community-based) or `3`. :type version: int :param secname: Security name to use for SNMPv3 only. :type secname: str :param authprotocol: Authorization protocol to use for SNMPv3. This can be `None` or either the string `SHA` or `MD5`. :type authprotocol: None or str :param authpassword: Authorization password if authorization protocol is not `None`. :type authpassword: str :param privprotocol: Privacy protocol to use for SNMPv3. This can be `None` or either the string `AES`, `AES128`, `AES192`, `AES256` or `3DES`. :type privprotocol: None or str :param privpassword: Privacy password if privacy protocol is not `None`. :type contextname: str :param contextname: Context name for SNMPv3 messages. :type privpassword: str :param bulk: Max repetition value for `GETBULK` requests. Set to `0` to disable. :type bulk: int :param none: When enabled, will return None for not found values (instead of raising an exception) :type none: bool """ self._host = host self._version = version self._none = none if version == 3: self._cmdgen = cmdgen.CommandGenerator() self._contextname = contextname else: if not hasattr(self._tls, "cmdgen"): self._tls.cmdgen = cmdgen.CommandGenerator() self._cmdgen = self._tls.cmdgen self._contextname = None if version == 1 and none: raise ValueError("None-GET requests not compatible with SNMPv1") # Put authentication stuff in self._auth if version in [1, 2]: self._auth = cmdgen.CommunityData( community, community, version - 1) elif version == 3: if secname is None: secname = community try: authprotocol = { None: cmdgen.usmNoAuthProtocol, "MD5": cmdgen.usmHMACMD5AuthProtocol, "SHA": cmdgen.usmHMACSHAAuthProtocol, "SHA1": cmdgen.usmHMACSHAAuthProtocol }[authprotocol] except KeyError: raise ValueError("{0} is not an acceptable authentication " "protocol".format(authprotocol)) try: privprotocol = { None: cmdgen.usmNoPrivProtocol, "DES": cmdgen.usmDESPrivProtocol, "3DES": cmdgen.usm3DESEDEPrivProtocol, "AES": cmdgen.usmAesCfb128Protocol, "AES128": cmdgen.usmAesCfb128Protocol, "AES192": cmdgen.usmAesCfb192Protocol, "AES256": cmdgen.usmAesCfb256Protocol, }[privprotocol] except KeyError: raise ValueError("{0} is not an acceptable privacy " "protocol".format(privprotocol)) self._auth = cmdgen.UsmUserData(secname, authpassword, privpassword, authprotocol, privprotocol) else: raise ValueError("unsupported SNMP version {0}".format(version)) # Put transport stuff into self._transport mo = re.match(r'^(?:' r'\[(?P[\d:A-Fa-f]+)\]|' r'(?P[\d\.]+)|' r'(?P.*?))' r'(?::(?P\d+))?$', host) if mo.group("port"): port = int(mo.group("port")) else: port = 161 if mo.group("ipv6"): self._transport = cmdgen.Udp6TransportTarget((mo.group("ipv6"), port)) elif mo.group("ipv4"): self._transport = cmdgen.UdpTransportTarget((mo.group("ipv4"), port)) else: results = socket.getaddrinfo(mo.group("any"), port, 0, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # We should try to connect to each result to determine if # the given family is available. However, we cannot do # that over UDP. Let's implement a safe choice. If we have # an IPv4 address, use that. If not, use IPv6. If we want # to add an option to force IPv6, it is a good place. if [x for x in results if x[0] == socket.AF_INET]: self._transport = cmdgen.UdpTransportTarget((mo.group("any"), port)) else: self._transport = cmdgen.Udp6TransportTarget((mo.group("any"), port)) # Bulk stuff self.bulk = bulk def _check_exception(self, value): """Check if the given ASN1 value is an exception""" if isinstance(value, rfc1905.NoSuchObject): raise SNMPNoSuchObject("No such object was found") # noqa: F821 if isinstance(value, rfc1905.NoSuchInstance): raise SNMPNoSuchInstance("No such instance exists") # noqa: F821 if isinstance(value, rfc1905.EndOfMibView): raise SNMPEndOfMibView("End of MIB was reached") # noqa: F821 def _convert(self, value): """Convert a PySNMP value to some native Python type""" try: # With PySNMP 4.3+, an OID is a ObjectIdentity. We try to # extract it while being compatible with earlier releases. value = value.getOid() except AttributeError: pass convertors = {rfc1902.Integer: int, rfc1902.Integer32: int, rfc1902.OctetString: bytes, rfc1902.IpAddress: value.prettyOut, rfc1902.Counter32: int, rfc1902.Counter64: int, rfc1902.Gauge32: int, rfc1902.Unsigned32: int, rfc1902.TimeTicks: int, rfc1902.Bits: str, rfc1902.Opaque: str, rfc1902.univ.ObjectIdentifier: tuple} if self._none: convertors[rfc1905.NoSuchObject] = lambda x: None convertors[rfc1905.NoSuchInstance] = lambda x: None for cl, fn in convertors.items(): if isinstance(value, cl): return fn(value) self._check_exception(value) raise NotImplementedError("unable to convert {0}".format(repr(value))) def _op(self, cmd, *oids): """Apply an SNMP operation""" kwargs = {} if self._contextname: kwargs['contextName'] = rfc1902.OctetString(self._contextname) errorIndication, errorStatus, errorIndex, varBinds = cmd( self._auth, self._transport, *oids, **kwargs) if errorIndication: self._check_exception(errorIndication) raise SNMPException(str(errorIndication)) if errorStatus: # We try to find a builtin exception with the same message exc = str(errorStatus.prettyPrint()) exc = re.sub(r'\W+', '', exc) exc = "SNMP{0}".format(exc[0].upper() + exc[1:]) if str(exc) in globals(): raise globals()[exc] raise SNMPException(errorStatus.prettyPrint()) if cmd in [self._cmdgen.getCmd, self._cmdgen.setCmd]: results = [(tuple(name), val) for name, val in varBinds] else: results = [(tuple(name), val) for row in varBinds for name, val in row] if len(results) > 0 and isinstance(results[-1][1], rfc1905.EndOfMibView): results = results[:-1] if len(results) == 0: if cmd not in [self._cmdgen.nextCmd, self._cmdgen.bulkCmd]: raise SNMPException("empty answer") return tuple([(oid, self._convert(val)) for oid, val in results]) def get(self, *oids): """Retrieve an OID value using GET. :param oids: a list of OID to retrieve. An OID is a tuple. :return: a list of tuples with the retrieved OID and the raw value. """ return self._op(self._cmdgen.getCmd, *oids) def walkmore(self, *oids): """Retrieve OIDs values using GETBULK or GETNEXT. The method is called "walk" but this is either a GETBULK or a GETNEXT. The later is only used for SNMPv1 or if bulk has been disabled using :meth:`bulk` property. :param oids: a list of OID to retrieve. An OID is a tuple. :return: a list of tuples with the retrieved OID and the raw value. """ if self._version == 1 or not self.bulk: return self._op(self._cmdgen.nextCmd, *oids) args = [0, self.bulk] + list(oids) try: return self._op(self._cmdgen.bulkCmd, *args) except SNMPTooBig: # Let's try to ask for less values. We will never increase # bulk again. We cannot increase it just after the walk # because we may end up requesting everything twice (or # more). nbulk = self.bulk / 2 or False if nbulk != self.bulk: self.bulk = nbulk return self.walk(*oids) raise def walk(self, *oids): """Walk from given OIDs but don't return any "extra" results. Only results in the subtree will be returned. :param oid: OIDs used as a start point :return: a list of tuples with the retrieved OID and the raw value. """ return ((noid, result) for oid in oids for noid, result in self.walkmore(oid) if (len(noid) >= len(oid) and noid[:len(oid)] == oid[:len(oid)])) def set(self, *args): """Set an OID value using SET. This function takes an odd number of arguments. They are working by pair. The first member is an OID and the second one is :class:`basictypes.Type` instace whose `pack()` method will be used to transform into the appropriate form. :return: a list of tuples with the retrieved OID and the raw value. """ if len(args) % 2 != 0: raise ValueError("expect an even number of arguments for SET") varbinds = zip(*[args[0::2], [v.pack() for v in args[1::2]]]) return self._op(self._cmdgen.setCmd, *varbinds) def __repr__(self): return "{0}(host={1},version={2})".format( self.__class__.__name__, self._host, self._version) @property def timeout(self): """Get timeout value for the current session. :return: Timeout value in microseconds. """ return self._transport.timeout * 1000000 @timeout.setter def timeout(self, value): """Set timeout value for the current session. :param value: Timeout value in microseconds. """ value = int(value) if value <= 0: raise ValueError("timeout is a positive integer") self._transport.timeout = value / 1000000. @property def retries(self): """Get number of times a request is retried. :return: Number of retries for each request. """ return self._transport.retries @retries.setter def retries(self, value): """Set number of times a request is retried. :param value: Number of retries for each request. """ value = int(value) if value < 0: raise ValueError("retries is a non-negative integer") self._transport.retries = value @property def bulk(self): """Get bulk settings. :return: `False` if bulk is disabled or a non-negative integer for the number of repetitions. """ return self._bulk @bulk.setter def bulk(self, value): """Set bulk settings. :param value: `False` to disable bulk or a non-negative integer for the number of allowed repetitions. """ if value is False: self._bulk = False return value = int(value) if value <= 0: raise ValueError("{0} is not an appropriate value " "for max repeater parameter".format( value)) self._bulk = value snimpy-0.8.13/snimpy/__main__.py0000644000076400001440000000161113357726113017200 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # if __name__ == "__main__": # pragma: no cover from snimpy import main main.interact() snimpy-0.8.13/snimpy/main.py0000644000076400001440000001326413357726113016413 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # We are using readline module of Python. Depending on the Python # distribution, this module may be linked to GNU Readline which is # GPLv2 licensed. """Provide an interactive shell for snimpy. The main method is C{interact()}. It will either use IPython if available or just plain python Shell otherwise. It will also try to use readline if available. For IPython, there is some configuration stuff to use a special profile. This is the recommended way to use it. It allows a separate history. """ import sys import os import atexit import code from datetime import timedelta import snimpy from snimpy import manager from snimpy.config import Conf def interact(argv=sys.argv): # pragma: no cover conf = Conf().load() banner = "\033[1mSnimpy\033[0m ({0}) -- {1}.\n".format( snimpy.__version__, snimpy.__doc__) banner += " load -> load an additional MIB\n" banner += " M -> manager object" local = {"conf": conf, "M": manager.Manager, "load": manager.load, "timedelta": timedelta, "snmp": manager.snmp } if len(argv) <= 1: manager.Manager._complete = True for ms in conf.mibs: manager.load(ms) globals().update(local) if len(argv) > 1: argv = argv[1:] exec(compile(open(argv[0]).read(), argv[0], 'exec')) in local return try: try: try: # ipython >= 1.0 from IPython.terminal.embed import \ InteractiveShellEmbed except ImportError: # ipython >= 0.11 from IPython.frontend.terminal.embed import \ InteractiveShellEmbed import IPython if IPython.version_info < (4,): from IPython.config.loader import Config else: from traitlets.config.loader import Config cfg = Config() try: # >= 5 from IPython.terminal.prompts import Prompts, Token class SnimpyPrompt(Prompts): def in_prompt_tokens(self, cli=None): return [ (Token.Prompt, "Snimpy["), (Token.PromptNum, str(self.shell.execution_count)), (Token.Prompt, ']> '), ] def out_prompt_tokens(self): return [ (Token.OutPrompt, "Snimpy["), (Token.OutPromptNum, str(self.shell.execution_count)), (Token.OutPrompt, ']: '), ] except ImportError: SnimpyPrompt = None try: # >= 0.12 cfg.PromptManager.in_template = "Snimpy [\\#]> " cfg.PromptManager.out_template = "Snimpy [\\#]: " except ImportError: # 0.11 cfg.InteractiveShellEmbed.prompt_in1 = "Snimpy [\\#]> " cfg.InteractiveShellEmbed.prompt_out = "Snimpy [\\#]: " if conf.ipythonprofile: cfg.InteractiveShellEmbed.profile = conf.ipythonprofile shell = InteractiveShellEmbed( config=cfg, banner1=banner, user_ns=local) # Not interested by traceback in this module shell.InteractiveTB.tb_offset += 1 if SnimpyPrompt is not None: shell.prompts = SnimpyPrompt(shell) except ImportError: # ipython < 0.11 from IPython.Shell import IPShellEmbed argv = ["-prompt_in1", "Snimpy [\\#]> ", "-prompt_out", "Snimpy [\\#]: "] if conf.ipythonprofile: argv += ["-profile", conf.ipythonprofile] shell = IPShellEmbed(argv=argv, banner=banner, user_ns=local) # Not interested by traceback in this module shell.IP.InteractiveTB.tb_offset += 1 except ImportError: shell = None if shell and conf.ipython: shell() else: try: import rlcompleter import readline except ImportError: readline = None if readline: if conf.histfile: try: readline.read_history_file( os.path.expanduser(conf.histfile)) except IOError: pass atexit.register(lambda: readline.write_history_file( os.path.expanduser(conf.histfile))) readline.set_completer(rlcompleter.Completer(local).complete) readline.parse_and_bind("tab: menu-complete") sys.ps1 = conf.prompt code.interact(banner=banner, local=local) snimpy-0.8.13/snimpy/smi_build.py0000644000076400001440000001213213357726113017427 0ustar bernatusers00000000000000# # snimpy -- Interactive SNMP tool # # Copyright (C) 2015 Vincent Bernat # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # """This module just exports libsmi through CFFI_. .. _CFFI: http://cffi.readthedocs.io/ """ from cffi import FFI _CDEF = """ typedef char *SmiIdentifier; typedef unsigned long SmiUnsigned32; typedef long SmiInteger32; typedef unsigned long long SmiUnsigned64; typedef long long SmiInteger64; typedef unsigned int SmiSubid; typedef float SmiFloat32; typedef double SmiFloat64; typedef long double SmiFloat128; typedef enum SmiBasetype { SMI_BASETYPE_INTEGER32, SMI_BASETYPE_OCTETSTRING, SMI_BASETYPE_OBJECTIDENTIFIER, SMI_BASETYPE_UNSIGNED32, SMI_BASETYPE_INTEGER64, SMI_BASETYPE_UNSIGNED64, SMI_BASETYPE_ENUM, SMI_BASETYPE_BITS, ... } SmiBasetype; typedef struct SmiType { SmiIdentifier name; SmiBasetype basetype; char *format; ...; } SmiType; typedef enum SmiIndexkind { SMI_INDEX_INDEX, SMI_INDEX_AUGMENT, ... } SmiIndexkind; typedef enum SmiAccess { SMI_ACCESS_NOT_IMPLEMENTED, SMI_ACCESS_NOT_ACCESSIBLE, SMI_ACCESS_READ_ONLY, SMI_ACCESS_READ_WRITE, ... } SmiAccess; typedef unsigned int SmiNodekind; #define SMI_NODEKIND_NODE ... #define SMI_NODEKIND_SCALAR ... #define SMI_NODEKIND_TABLE ... #define SMI_NODEKIND_ROW ... #define SMI_NODEKIND_COLUMN ... #define SMI_NODEKIND_NOTIFICATION ... typedef struct SmiNode { SmiIdentifier name; unsigned int oidlen; SmiSubid *oid; char *format; SmiIndexkind indexkind; int implied; SmiNodekind nodekind; SmiAccess access; ...; } SmiNode; typedef struct SmiValue { SmiBasetype basetype; union { SmiUnsigned64 unsigned64; SmiInteger64 integer64; SmiUnsigned32 unsigned32; SmiInteger32 integer32; SmiFloat32 float32; SmiFloat64 float64; SmiFloat128 float128; SmiSubid *oid; char *ptr; } value; ...; } SmiValue; typedef struct SmiRange { SmiValue minValue; SmiValue maxValue; } SmiRange; typedef struct SmiModule { SmiIdentifier name; int conformance; ...; } SmiModule; typedef struct SmiElement { ...; } SmiElement; typedef struct SmiNamedNumber { SmiIdentifier name; SmiValue value; } SmiNamedNumber; typedef void (SmiErrorHandler) (char *path, int line, int severity, char *msg, char *tag); int smiInit(const char *); void smiExit(void); void smiSetErrorLevel(int); void smiSetErrorHandler(SmiErrorHandler *); void smiSetFlags(int); int smiSetPath(const char *); char *smiGetPath(void); char *smiLoadModule(const char *); SmiModule *smiGetFirstModule(void); SmiModule *smiGetNextModule(SmiModule *); SmiModule *smiGetModule(const char *); SmiModule *smiGetNodeModule(SmiNode *); SmiType *smiGetNodeType(SmiNode *); SmiType *smiGetParentType(SmiType *); SmiType *smiGetType(SmiModule *, char *); SmiModule *smiGetTypeModule(SmiType *); char *smiRenderNode(SmiNode *, int); SmiElement *smiGetFirstElement(SmiNode *); SmiElement *smiGetNextElement(SmiElement *); SmiNode *smiGetElementNode(SmiElement *); SmiRange *smiGetFirstRange(SmiType *); SmiRange *smiGetNextRange(SmiRange *); SmiNode *smiGetNode(SmiModule *, const char *); SmiNode *smiGetNodeByOID(unsigned int oidlen, SmiSubid *); SmiNode *smiGetFirstNode(SmiModule *, SmiNodekind); SmiNode *smiGetNextNode(SmiNode *, SmiNodekind); SmiNode *smiGetParentNode(SmiNode *); SmiNode *smiGetRelatedNode(SmiNode *); SmiNode *smiGetFirstChildNode(SmiNode *); SmiNode *smiGetNextChildNode(SmiNode *); SmiNamedNumber *smiGetFirstNamedNumber(SmiType *); SmiNamedNumber *smiGetNextNamedNumber(SmiNamedNumber *); void free(void *); #define SMI_FLAG_ERRORS ... #define SMI_FLAG_RECURSIVE ... #define SMI_RENDER_ALL ... """ _SOURCE = """ #include """ ffi = FFI() ffi.cdef(_CDEF) if hasattr(ffi, 'set_source'): ffi.set_source("snimpy._smi", _SOURCE, libraries=["smi"]) def get_lib(): return ffi.verify(_SOURCE, libraries=["smi"]) if __name__ == "__main__": ffi.compile() snimpy-0.8.13/snimpy/__init__.py0000644000076400001440000000030513357726113017216 0ustar bernatusers00000000000000"""interactive SNMP tool""" __author__ = 'Vincent Bernat' __email__ = 'bernat@luffy.cx' try: from snimpy._version import __version__ # nopep8 except ImportError: __version__ = '0.0~dev' snimpy-0.8.13/.gitignore0000644000076400001440000000026513012531051015542 0ustar bernatusers00000000000000*.pyc *~ *.so /version.txt /build /snimpy.egg-info /snimpy/_version.py /*.egg /.eggs/ /dist /MANIFEST /.coverage /htmlcov /.tox /.noseids /docs/_build __pycache__ .index cscope.out snimpy-0.8.13/README.rst0000644000076400001440000000371213012531051015241 0ustar bernatusers00000000000000=============================== snimpy =============================== .. image:: https://badge.fury.io/py/snimpy.png :target: http://badge.fury.io/py/snimpy .. image:: https://travis-ci.org/vincentbernat/snimpy.png?branch=master :target: https://travis-ci.org/vincentbernat/snimpy .. image:: https://coveralls.io/repos/vincentbernat/snimpy/badge.png :target: https://coveralls.io/r/vincentbernat/snimpy Interactive SNMP tool. *Snimpy* is a Python-based tool providing a simple interface to build SNMP query. Here is a very simplistic example that allows us to display the routing table of a given host:: load("IP-FORWARD-MIB") m=M("localhost", "public", 2) routes = m.ipCidrRouteNextHop for x in routes: net, netmask, tos, src = x print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src)) You can either use *Snimpy* interactively throught its console (derived from Python own console or from IPython_ if available) or write *Snimpy* scripts which are just Python scripts with some global variables available. .. _IPython: http://ipython.org * Free software: ISC license * Documentation: http://snimpy.rtfd.org. *Snimpy* requires libsmi_ to work correctly. See the documentation for more information. .. _libsmi: https://www.ibr.cs.tu-bs.de/projects/libsmi/ Features -------- *Snimpy* is aimed at being the more Pythonic possible. You should forget that you are doing SNMP requests. *Snimpy* will rely on MIB to hide SNMP details. Here are some "features": * MIB parser based on libsmi (through CFFI) * SNMP requests are handled by PySNMP (SNMPv1, SNMPv2 and SNMPv3 support) * scalars are just attributes of your session object * columns are like a Python dictionary and made available as an attribute * getting an attribute is like issuing a GET method * setting an attribute is like issuing a SET method * iterating over a table is like using GETNEXT * when something goes wrong, you get an exception