snimpy-0.8.1/0000755000175000017500000000000012232211722013601 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/0000755000175000017500000000000012232211722014531 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/license.rst0000644000175000017500000000174512230342274016721 0ustar bernatbernat00000000000000======== 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.1/docs/_static/0000755000175000017500000000000012232211722016157 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/_static/snimpy.svg0000644000175000017500000002744312230062722020233 0ustar bernatbernat00000000000000 image/svg+xml snimpy-0.8.1/docs/contributing.rst0000644000175000017500000000004012226612424017773 0ustar bernatbernat00000000000000.. include:: ../CONTRIBUTING.rstsnimpy-0.8.1/docs/index.rst0000644000175000017500000000413312230342274016400 0ustar bernatbernat00000000000000Snimpy: 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 * raise exceptions when something goes wrong Contents: --------- .. toctree:: :maxdepth: 1 installation usage api contributing license history snimpy-0.8.1/docs/conf.py0000644000175000017500000000235312230342274016040 0ustar bernatbernat00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys, 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() 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'2013, 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.1/docs/history.rst0000644000175000017500000000003312226612424016767 0ustar bernatbernat00000000000000.. include:: ../HISTORY.rstsnimpy-0.8.1/docs/api.rst0000644000175000017500000000113712230342274016043 0ustar bernatbernat00000000000000============== 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. :mod:`manager` module ---------------------- .. automodule:: snimpy.manager :members: Manager, load :mod:`mib` module ------------------ .. automodule:: snimpy.mib :members: :mod:`snmp` module ------------------- .. automodule:: snimpy.snmp :members: :mod:`basictypes` module ------------------------- .. automodule:: snimpy.basictypes :members: snimpy-0.8.1/docs/_themes/0000755000175000017500000000000012232211722016155 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/_themes/flask_theme_support.py0000644000175000017500000001141312226612364022617 0ustar bernatbernat00000000000000# 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.1/docs/_themes/flask/0000755000175000017500000000000012232211722017255 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/_themes/flask/layout.html0000644000175000017500000000126512226612364021476 0ustar bernatbernat00000000000000{%- 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.1/docs/_themes/flask/static/0000755000175000017500000000000012232211722020544 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/_themes/flask/static/flasky.css_t0000644000175000017500000002154612226612364023114 0ustar bernatbernat00000000000000/* * 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.1/docs/_themes/flask/relations.html0000644000175000017500000000111612226612364022154 0ustar bernatbernat00000000000000

Related Topics

snimpy-0.8.1/docs/_themes/flask/theme.conf0000644000175000017500000000024412226612364021240 0ustar bernatbernat00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px touch_icon = snimpy-0.8.1/docs/_themes/flask_small/0000755000175000017500000000000012232211722020445 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/_themes/flask_small/layout.html0000644000175000017500000000125312226612364022663 0ustar bernatbernat00000000000000{% 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.1/docs/_themes/flask_small/static/0000755000175000017500000000000012232211722021734 5ustar bernatbernat00000000000000snimpy-0.8.1/docs/_themes/flask_small/static/flasky.css_t0000644000175000017500000001100112226612364024265 0ustar bernatbernat00000000000000/* * 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.1/docs/_themes/flask_small/theme.conf0000644000175000017500000000027012226612364022427 0ustar bernatbernat00000000000000[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.1/docs/_themes/README0000644000175000017500000000210512226612364017045 0ustar bernatbernat00000000000000Flask 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.1/docs/_themes/.gitignore0000644000175000017500000000002612226612364020155 0ustar bernatbernat00000000000000*.pyc *.pyo .DS_Store snimpy-0.8.1/docs/_themes/LICENSE0000644000175000017500000000337512226612364017204 0ustar bernatbernat00000000000000Copyright (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.1/docs/installation.rst0000644000175000017500000000046512230342274017776 0ustar bernatbernat00000000000000============ Installation ============ At the command line:: $ easy_install snimpy Or, if you have virtualenvwrapper installed:: $ mkvirtualenv snimpy $ pip install snimpy On Debian and Ubuntu, *Snimpy* is also available as a package you can install with:: $ sudo apt-get install snimpy snimpy-0.8.1/docs/usage.rst0000644000175000017500000001303112230342274016372 0ustar bernatbernat00000000000000======== 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.ifDescr: print repr(m.ifDescr[i]) m.ifAdminStatus[3] = "down" 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" There is also a 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) 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, retry=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) 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.1/snimpy/0000755000175000017500000000000012232211722015120 5ustar bernatbernat00000000000000snimpy-0.8.1/snimpy/mib.py0000644000175000017500000004717712232161766016275 0ustar bernatbernat00000000000000# # 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.org/ """ from snimpy import cffi_fix # nopep8 from cffi import FFI ffi = FFI() 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 unsigned int SmiNodekind; #define SMI_NODEKIND_NODE ... #define SMI_NODEKIND_SCALAR ... #define SMI_NODEKIND_TABLE ... #define SMI_NODEKIND_ROW ... #define SMI_NODEKIND_COLUMN ... typedef struct SmiNode { SmiIdentifier name; unsigned int oidlen; SmiSubid *oid; char *format; SmiIndexkind indexkind; int implied; SmiNodekind nodekind; ...; } 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; int smiInit(const char *); void smiExit(void); void smiSetErrorLevel(int); void smiSetFlags(int); char *smiLoadModule(const char *); SmiModule *smiGetModule(const char *); SmiModule *smiGetNodeModule(SmiNode *); SmiType *smiGetNodeType(SmiNode *); SmiType *smiGetParentType(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 *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 *); #define SMI_FLAG_ERRORS ... #define SMI_FLAG_RECURSIVE ... #define SMI_RENDER_ALL ... """) _smi = ffi.verify(""" #include """, libraries=["smi"]) 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 @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 t = _smi.smiGetNodeType(self.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 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. """ 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 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))) 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 def reset(): """Reset libsmi to its initial state.""" _smi.smiExit() if _smi.smiInit(b"snimpy") < 0: raise SMIException("unable to init libsmi") _smi.smiSetErrorLevel(0) try: _smi.smiSetFlags(_smi.SMI_FLAG_ERRORS | _smi.SMI_FLAG_RECURSIVE) except TypeError: pass # We are being mocked 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_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 _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 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 load {0}".format(mib)) modulename = ffi.string(modulename) if not _get_module(modulename.decode("ascii")): raise SMIException("{0} contains major SMI error " "(check with smilint -s -l1)".format(mib)) return modulename reset() snimpy-0.8.1/snimpy/manager.py0000644000175000017500000003360512230342274017120 0ustar bernatbernat00000000000000# # 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] """ from time import time from collections import MutableMapping 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 = {} self.timeout = timeout self.count = 0 def getorgetnext(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] self.flush() return value def get(self, *args): return self.getorgetnext("get", *args) def getnext(self, *args): return self.getorgetnext("getnext", *args) def flush(self): if self.count < 1000: return keys = self.cache.keys() for k in keys: if time() - self.cache[k][0] > self.timeout: del self.cache[k] self.count = 0 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]) 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, # SNMPv3 secname=None, authprotocol=None, authpassword=None, privprotocol=None, privpassword=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 """ if host is None: host = Manager._host self._host = host self._session = snmp.Session(host, community, version, secname, authprotocol, authpassword, privprotocol, privpassword) 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) def _locate(self, attribute): for m in 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: return a.type(a, result) return None elif isinstance(a, mib.Column): return ProxyColumn(self._session, a) 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 __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: def __repr__(self): return "<{0} for {1}>".format(self.__class__.__name__, repr(self.proxy)[1:-1]) class ProxyColumn(Proxy, MutableMapping): """Proxy for column access""" def __init__(self, session, column): self.proxy = column self.session = session 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) oidindex.extend(ind.toOid()) result = getattr( self.session, op)(self.proxy.oid + tuple(oidindex), *args) if op != "set": oid, result = result[0] if result is not None: return self.proxy.type(self.proxy, result) return None def __getitem__(self, index): return self._op("get", index) def __setitem__(self, index, value): if not isinstance(value, basictypes.Type): value = self.proxy.type(self.proxy, value, raw=False) self._op("set", index, value) def __delitem__(self, index): raise NotImplementedError("cannot suppress a column") def keys(self): return [k for k in self] def has_key(self, object): try: self._op("get", object) except: return False return True def __iter__(self): for k, _ in self.iteritems(): yield k def __len__(self): len(list(self.iteritems())) def iteritems(self): count = 0 oid = self.proxy.oid indexes = self.proxy.table.index for noid, result in self.session.walk(oid): if noid <= oid: noid = None break oid = noid if not((len(oid) >= len(self.proxy.oid) and oid[:len(self.proxy.oid)] == self.proxy.oid[:len(self.proxy.oid)])): noid = None break # oid should be turned into index index = oid[len(self.proxy.oid):] target = [] for x in indexes: l, o = x.type.fromOid(x, tuple(index)) target.append(x.type(x, o)) index = index[l:] count = count + 1 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 raise StopIteration except snmp.SNMPNoSuchName: # SNMPv1, we don't know pass except snmp.SNMPNoSuchObject: # The result is empty because the column is unknown raise raise StopIteration 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): setattr(Manager, str(o), 1) snimpy-0.8.1/snimpy/__main__.py0000644000175000017500000000161112225620541017216 0ustar bernatbernat00000000000000# # 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.1/snimpy/main.py0000644000175000017500000001051012225620542016421 0ustar bernatbernat00000000000000# # 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 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: import rlcompleter import readline except ImportError: readline = None try: try: # ipython >= 0.11 from IPython.frontend.terminal.embed import InteractiveShellEmbed from IPython.config.loader import Config cfg = Config() 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 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: 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.1/snimpy/__init__.py0000644000175000017500000000015712232211704017234 0ustar bernatbernat00000000000000"""interactive SNMP tool""" __author__ = 'Vincent Bernat' __email__ = 'bernat@luffy.cx' __version__ = '0.8.1' snimpy-0.8.1/snimpy/cffi_fix.py0000644000175000017500000000372712232166465017275 0ustar bernatbernat00000000000000# Copyright 2013 Donald Stufft and individual contributors # Copyright 2013 Vincent Bernat # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # The original version is available here: # https://github.com/pyca/pynacl/blob/master/src/nacl/_cffi_fix.py # # This modified version will just call original implementation but # with additional suffixes if needed. import imp import sys import functools def get_so_suffixes(): suffixes = [suffix for suffix, _, t in imp.get_suffixes() if t == imp.C_EXTENSION] if not suffixes: # bah, no C_EXTENSION available. Occurs on pypy without cpyext if sys.platform == 'win32': suffixes = [".pyd"] else: suffixes = [".so"] return suffixes def patch(cls): """Patch `find_module` method to try more suffixes.""" original_find_module = cls.find_module @functools.wraps(original_find_module) def find_more_modules(self, module_name, path, so_suffix): suffixes = get_so_suffixes() if so_suffix in suffixes: suffixes.remove(so_suffix) suffixes.insert(0, so_suffix) for suffix in suffixes: filename = original_find_module(self, module_name, path, suffix) if filename is not None: return filename return None cls.find_module = find_more_modules import cffi.vengine_cpy import cffi.vengine_gen patch(cffi.vengine_cpy.VCPythonEngine) patch(cffi.vengine_gen.VGenericEngine) snimpy-0.8.1/snimpy/basictypes.py0000644000175000017500000007314612230342274017660 0ustar bernatbernat00000000000000# # 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: ord2 = lambda x: x chr2 = lambda x: bytes([x]) unicode = str long = int else: ord2 = ord chr2 = chr 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 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): """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): """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 _fixedOrImplied(cls, entity): """Determine if the given entity is fixed-len or implied. This function is an 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. :param entity: entity to check :return: "fixed" if it is fixed-len, "implied" if implied var-len, `False` otherwise """ if entity.ranges and not isinstance(entity.ranges, (tuple, list)): # Fixed length return "fixed" # We have a variable-len string/oid. We need to know if it is implied. try: table = entity.table except: raise NotImplementedError( "{0} is not an index of a table".format(entity)) indexes = [str(a) for a in table.index] if str(entity) not in indexes: raise NotImplementedError( "{0} is not an index of a table".format(entity)) if str(entity) != indexes[-1] or not table.implied: # This index is not implied return False return "implied" def __str__(self): return str(self._value) def __repr__(self): try: return '<{0}: {1}>'.format(self.__class__.__name__, str(self)) except: return '<{0} ????>'.format(self.__class__.__name__) @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): try: value = socket.inet_ntoa(value) except: pass try: value = socket.inet_ntoa(socket.inet_aton(value)) except: 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): return tuple(self._value) @classmethod def fromOid(cls, entity, oid): 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: 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): # 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 self._fixedOrImplied(self.entity): return tuple(ord2(a) for a in b) return tuple([len(b)] + [ord2(a) for a in b]) def _toBytes(self): raise NotImplementedError @classmethod def fromOid(cls, entity, oid): type = cls._fixedOrImplied(entity) if type == "implied": # Eat everything return (len(oid), cls(entity, b"".join([chr2(x) for x in oid]))) if type == "fixed": l = entity.ranges if len(oid) < l: raise ValueError( "{0} is too short for wanted fixed " "string (need at least {1:d})".format(oid, l)) return (l, cls(entity, b"".join([chr2(x) for x in oid[:l]]))) # This is var-len if not oid: raise ValueError("empty OID while waiting for var-len string") l = oid[0] if len(oid) < l + 1: raise ValueError( "{0} is too short for variable-len " "string (need at least {1:d})".format(oid, l)) return ( (l + 1, cls(entity, b"".join([chr2(x) for x in oid[1:(l + 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 = "" 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.{{1,{0}}})".format(length) elif format == "t": fmatch = "(?P.{{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 {i}".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): return (self._value,) @classmethod def fromOid(cls, entity, oid): 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: 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): 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: 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): if self._fixedOrImplied(self.entity): return self._value return tuple([len(self._value)] + list(self._value)) @classmethod def fromOid(cls, entity, oid): if cls._fixedOrImplied(entity) == "fixed": # 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 cls._fixedOrImplied(entity): # 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)) l = oid[0] if len(oid) < l + 1: raise ValueError( "{0!r} has an incorrect size " "(needs at least {1:d})".format(oid, l)) return (l + 1, cls(entity, oid[1:(l + 1)])) else: # 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 __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): return (int(self),) @classmethod def fromOid(cls, entity, oid): 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)): if j not in entity.enum: tryalternate = True break bits.add(j) 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): string = [] for b in self._value: if len(string) < (b >> 4) + 1: string.extend([0] * ((b >> 4) - len(string) + 1)) string[b >> 416] |= 1 << (7 - b % 16) 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.1/snimpy/config.py0000644000175000017500000000320112225620542016741 0ustar bernatbernat00000000000000# # 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 = [] conf = Conf() # Load user configuration file if conf.userconf: try: conffile = open(os.path.expanduser(conf.userconf)) except (OSError, IOError): pass else: try: confuser = imp.load_module("confuser", conffile, os.path.expanduser(conf.userconf), ("conf", 'r', imp.PY_SOURCE)) for k in confuser.__dict__: if not k.startswith("__"): conf.__dict__[k] = confuser.__dict__[k] finally: conffile.close() snimpy-0.8.1/snimpy/snmp.py0000644000175000017500000002761612230342274016470 0ustar bernatbernat00000000000000# # 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 inspect 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.""" def __init__(self, host, community="public", version=2, secname=None, authprotocol=None, authpassword=None, privprotocol=None, privpassword=None): """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 privpassword: str """ self._host = host self._version = version self._cmdgen = cmdgen.CommandGenerator() # 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 host, port = host.partition(":")[::2] if not port: port = 161 self._transport = cmdgen.UdpTransportTarget((host, int(port))) # Bulk stuff self.bulk = 40 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") # nopep8 if isinstance(value, rfc1905.NoSuchInstance): raise SNMPNoSuchInstance("No such instance exists") # nopep8 if isinstance(value, rfc1905.EndOfMibView): raise SNMPEndOfMibView("End of MIB was reached") # nopep8 def _convert(self, value): """Convert a PySNMP value to some native Python type""" for cl, fn in {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.univ.ObjectIdentifier: tuple}.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""" errorIndication, errorStatus, errorIndex, varBinds = cmd( self._auth, self._transport, *oids) 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: if cmd not in [self._cmdgen.nextCmd, self._cmdgen.bulkCmd]: raise SNMPException("empty answer") # This seems to be filtered raise SNMPEndOfMibView("no more stuff after this OID") # nopep8 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 walk(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) return self._op(self._cmdgen.bulkCmd, *args) 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.1/HISTORY.rst0000644000175000017500000000310412232211673015477 0ustar bernatbernat00000000000000.. :changelog: History ------- 0.8.0 (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.1/man/0000755000175000017500000000000012232211722014354 5ustar bernatbernat00000000000000snimpy-0.8.1/man/snimpy.10000644000175000017500000000176711763715346016013 0ustar bernatbernat00000000000000.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.1/CONTRIBUTING.rst0000644000175000017500000000611512226611646016261 0ustar bernatbernat00000000000000============ 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.6, 2.7, and 3.3, 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.1/setup.py0000644000175000017500000000332112230342274015317 0ustar bernatbernat00000000000000from setuptools import setup import snimpy try: import multiprocessing import pysnmp except ImportError: pass if __name__ == "__main__": # MIB module try: import snimpy.mib ext_modules = [ snimpy.mib.ffi.verifier.get_extension() ] except ImportError: ext_modules = [] readme = open('README.rst').read() history = open('HISTORY.rst').read().replace('.. :changelog:', '') setup(name="snimpy", version=snimpy.__version__, 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'])], ext_modules = ext_modules, zip_safe = False, install_requires = [ "cffi", "pysnmp >= 4" ], setup_requires = [ "cffi" ], tests_require = [ "cffi", "pysnmp >= 4", "nose", "mock" ], test_suite="nose.collector" ) snimpy-0.8.1/snimpy.egg-info/0000755000175000017500000000000012232211722016612 5ustar bernatbernat00000000000000snimpy-0.8.1/snimpy.egg-info/top_level.txt0000644000175000017500000000004112232211721021336 0ustar bernatbernat00000000000000_cffi__x679e504cxa0dd8598 snimpy snimpy-0.8.1/snimpy.egg-info/not-zip-safe0000644000175000017500000000000112232211721021037 0ustar bernatbernat00000000000000 snimpy-0.8.1/snimpy.egg-info/dependency_links.txt0000644000175000017500000000000112232211721022657 0ustar bernatbernat00000000000000 snimpy-0.8.1/snimpy.egg-info/requires.txt0000644000175000017500000000002012232211721021201 0ustar bernatbernat00000000000000cffi pysnmp >= 4snimpy-0.8.1/snimpy.egg-info/SOURCES.txt0000644000175000017500000000254212232211721020500 0ustar bernatbernat00000000000000AUTHORS.rst CONTRIBUTING.rst HISTORY.rst MANIFEST.in README.rst setup.py 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/basictypes.py snimpy/cffi_fix.py snimpy/config.py snimpy/main.py snimpy/manager.py snimpy/mib.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_main.py tests/test_manager.py tests/test_mib.py tests/test_snmp.pysnimpy-0.8.1/snimpy.egg-info/PKG-INFO0000644000175000017500000001215712232211721017714 0ustar bernatbernat00000000000000Metadata-Version: 1.1 Name: snimpy Version: 0.8.1 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://pypip.in/d/snimpy/badge.png :target: https://crate.io/packages/snimpy?version=latest 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. 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.0 (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.1/snimpy.egg-info/entry_points.txt0000644000175000017500000000006112232211721022104 0ustar bernatbernat00000000000000[console_scripts] snimpy = snimpy.main:interact snimpy-0.8.1/examples/0000755000175000017500000000000012232211722015417 5ustar bernatbernat00000000000000snimpy-0.8.1/examples/disable-port-ingress-filtering.py0000644000175000017500000000107212224343036024014 0ustar bernatbernat00000000000000#!/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.1/examples/set-syslog-ntp.py0000755000175000017500000000424712224343036020720 0ustar bernatbernat00000000000000#!/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.1/examples/vlan-and-interfaces.py0000644000175000017500000000116211730211606021615 0ustar bernatbernat00000000000000#!/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.1/examples/list-routes.py0000644000175000017500000000121212224343036020264 0ustar bernatbernat00000000000000#!/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.1/examples/enable-lldp.py0000644000175000017500000000343012224343036020155 0ustar bernatbernat00000000000000#!/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.1/examples/list-interfaces.py0000644000175000017500000000023112224343036021066 0ustar bernatbernat00000000000000#!/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.1/examples/get-serial.py0000644000175000017500000000163012224343036020032 0ustar bernatbernat00000000000000#!/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.1/examples/add-vlan.py0000644000175000017500000000236312224343036017470 0ustar bernatbernat00000000000000#!/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.1/examples/rename-vlan.py0000644000175000017500000000116012224343036020201 0ustar bernatbernat00000000000000#!/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.1/PKG-INFO0000644000175000017500000001215712232211722014704 0ustar bernatbernat00000000000000Metadata-Version: 1.1 Name: snimpy Version: 0.8.1 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://pypip.in/d/snimpy/badge.png :target: https://crate.io/packages/snimpy?version=latest 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. 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.0 (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.1/AUTHORS.rst0000644000175000017500000000020412230342273015460 0ustar bernatbernat00000000000000Development Lead ---------------- * Vincent Bernat Contributors ------------ * Jakub Wroniecki * Julian Taylor snimpy-0.8.1/tests/0000755000175000017500000000000012232211722014743 5ustar bernatbernat00000000000000snimpy-0.8.1/tests/test_main.py0000644000175000017500000000173312225620542017312 0ustar bernatbernat00000000000000import unittest import os import tempfile import code # nopep8 import mock from snimpy.main import interact from multiprocessing import Process import agent class TestMain(unittest.TestCase): """Test the main shell""" @classmethod def setUpClass(cls): cls.agent = agent.TestAgent() @classmethod def tearDownClass(cls): cls.agent.terminate() 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.1/tests/test_basictypes.py0000644000175000017500000005755312230342274020546 0ustar bernatbernat00000000000000import 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) # 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): (47,), ("snimpyComplexFirstIP", "10.14.15.4"): (10, 14, 15, 4), ("snimpyComplexSecondIP", (14, 15, 16, 17)): (14, 15, 16, 17), ("snimpyIndexOidVarLen", (47, 48, 49)): (3, 47, 48, 49), ("snimpyIndexVarLen", "hello1"): tuple([len("hello1")] + [ord(a) for a in "hello1"]), ("snimpyIndexFixedLen", "hello2"): tuple(ord(a) for a in "hello2"), ("snimpyIndexImplied", "hello3"): tuple(ord(a) for a in "hello3"), } for t, v in tt: oid = basictypes.build("SNIMPY-MIB", t, v).toOid() self.assertEqual(oid, tt[t, v]) # Test double conversion self.assertEqual(mib.get("SNIMPY-MIB", t).type.fromOid( mib.get("SNIMPY-MIB", t), oid), (len(tt[t, v]), v)) def testOidGreedy(self): """Test greediness of fromOid.""" tt = { "snimpyIndexVarLen": ((5, 104, 101, 108, 108, 111, 111, 111, 111), (6, "hello")), "snimpyIndexFixedLen": ((104, 101, 108, 108, 111, 49, 49, 111), (6, "hello1")), "snimpyIndexImplied": ((104, 101, 108, 108, 111, 50), (6, "hello2")), "snimpyComplexFirstIP": ((15, 15, 16, 100, 23, 74, 87), (4, "15.15.16.100")), "snimpySimpleIndex": ((17, 19, 20), (1, 17)), "snimpyIndexOidVarLen": ((3, 247, 145, 475568, 475, 263), (4, (247, 145, 475568))), } for t in tt: self.assertEqual(mib.get("SNIMPY-MIB", t).type.fromOid( mib.get("SNIMPY-MIB", t), tt[t][0]), tt[t][1]) # Test if too short tt = {"snimpyComplexFirstIP": (17, 19, 20), "snimpyIndexFixedLen": (104, 101, 108), "snimpyIndexVarLen": (6, 102, 103, 104, 105), "snimpyIndexOidVarLen": (3, 247, 145), } for t in tt: self.assertRaises(ValueError, mib.get("SNIMPY-MIB", t).type.fromOid, mib.get("SNIMPY-MIB", t), tt[t]) 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") 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") 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") 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.1/tests/test_manager.py0000644000175000017500000002616112225620542020002 0ustar bernatbernat00000000000000import unittest import os import time from datetime import timedelta from snimpy.manager import load, Manager, snmp import agent 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") 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"]) def testScalar_MacAddress(self): """Retrieve MacAddress as a scalar""" self.scalarGetAndCheck("snimpyMacAddress", "11:12:13:14:15:16") 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.ifIndex] self.assertEqual(results, [(1, "lo", 24), (2, "eth0", 6), (3, "eth1", 6)]) 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 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") 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"]) 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 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") snimpy-0.8.1/tests/SNIMPY-INVALID-MIB.mib0000644000175000017500000000116011763715346020165 0ustar bernatbernat00000000000000SNIMPY-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.1/tests/agent.py0000644000175000017500000002535612225620542016434 0ustar bernatbernat00000000000000from 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 from pysnmp.proto.api import v2c class TestAgent(object): """Agent for testing purpose""" def __init__(self): q = Queue() self._process = Process(target=self._setup, args=(q,)) self._process.start() self.port = q.get() def terminate(self): self._process.terminate() def _setup(self, q): """Setup a new agent in a separate process. The port the agent is listening too will be returned using the provided queue. """ port = random.randrange(22000, 22989) snmpEngine = engine.SnmpEngine() 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', 'public') 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, 'authpass', config.usmAesCfb128Protocol, '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') 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"))) 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::ifIndex ifIndex=MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 1), v2c.Integer())) mibBuilder.exportSymbols( '__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")), # 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::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::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)), # Indexes snimpyIndexVarLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1), v2c.OctetString( )).setMaxAccess("noaccess"), 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") ) # 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.1/tests/test_snmp.py0000644000175000017500000002475412225620542017353 0ustar bernatbernat00000000000000import unittest import os 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 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 setUpClass(cls): mib.load('IF-MIB') mib.load('SNMPv2-MIB') cls.agent = agent.TestAgent() def setUp(self): self.session = snmp.Session( host="127.0.0.1:{0}".format(self.agent.port), community="public", version=self.version) @classmethod def tearDownClass(cls): cls.agent.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") 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 testInexistant(self): """Get an inexistant value""" self.assertRaises( self.version == 1 and snmp.SNMPNoSuchName or snmp.SNMPNoSuchObject, self.session.get, (1, 2, 3)) 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") 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(results, ((ooid + (1,), b"lo"), (ooid + (2,), b"eth0"), (ooid + (3,), b"eth1"))) class TestSnmp2(TestSnmp1): """Test communication with an agent with SNMPv2.""" version = 2 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(results, ((ooid + (1,), b"lo"), (ooid + (2,), b"eth0"), (ooid + (3,), b"eth1"), (mib.get("IF-MIB", "ifType").oid + (1,), 24))) self.session.bulk = 2 results = self.session.walk(ooid) self.assertEqual(results[:2], ((ooid + (1,), b"lo"), (ooid + (2,), b"eth0"))) class TestSnmp3(TestSnmp2): """Test communicaton with an agent with SNMPv3.""" version = 3 def setUp(self): self.session = snmp.Session( host="127.0.0.1:{0}".format(self.agent.port), version=3, secname="read-write", authprotocol="MD5", authpassword="authpass", privprotocol="AES", privpassword="privpass") snimpy-0.8.1/tests/test_mib.py0000644000175000017500000002457612225620542017147 0ustar bernatbernat00000000000000import unittest import os from snimpy import mib, basictypes 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"] self.nodes.sort() self.tables = ["snimpyComplexTable", "snimpySimpleTable", "snimpyIndexTable"] self.tables.sort() self.columns = ["snimpyComplexFirstIP", "snimpyComplexSecondIP", "snimpySimpleIndex", "snimpyComplexState", "snimpySimpleDescr", "snimpySimplePhys", "snimpySimpleType", "snimpyIndexVarLen", "snimpyIndexIntIndex", "snimpyIndexOidVarLen", "snimpyIndexFixedLen", "snimpyIndexImplied", "snimpyIndexInt", ] self.columns.sort() self.scalars = ["snimpyIpAddress", "snimpyString", "snimpyInteger", "snimpyEnum", "snimpyObjectId", "snimpyBoolean", "snimpyCounter", "snimpyGauge", "snimpyTimeticks", "snimpyCounter64", "snimpyBits", "snimpyNotImplemented", "snimpyOctetString", "snimpyUnicodeString", "snimpyMacAddress"] self.scalars.sort() 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 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)) 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]) 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"}) 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"]) 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 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") snimpy-0.8.1/tests/SNIMPY-MIB.mib0000644000175000017500000002070312224343037017070 0ustar bernatbernat00000000000000SNIMPY-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 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 } 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) } 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 } -- 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 not-accessible 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 } END snimpy-0.8.1/setup.cfg0000644000175000017500000000007312232211722015422 0ustar bernatbernat00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 snimpy-0.8.1/MANIFEST.in0000644000175000017500000000064112232055305015343 0ustar bernatbernat00000000000000recursive-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 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.1/README.rst0000644000175000017500000000344512226621215015303 0ustar bernatbernat00000000000000=============================== 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://pypip.in/d/snimpy/badge.png :target: https://crate.io/packages/snimpy?version=latest 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. 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