snmpsim-1.2.2/CHANGES.rst0000644000000000000000000006734313615410400011741 0ustar00.. current-version: 1.2.2 (managed by bump2version) Revision 1.2.2, released on Apr 26, 2026 ---------------------------------------- - Updated pysnmp to 7.1.26. - Fixed a bug in cmd2rec.py. Revision 1.2.1, released on Nov 19, 2025 ---------------------------------------- - Fixed Python 3.14 compatibility issues. Revision 1.2.0, released on Nov 19, 2025 ---------------------------------------- - Upgraded pysnmp to 7.1. Revision 1.1.7, released on Sep 13, 2024 ---------------------------------------- - Removed pyasn1.compat dependency. Revision 1.1.6, released on Sep 12, 2024 ---------------------------------------- - Removed pyasn1.compat dependency. Revision 1.1.5, released on Aug 09, 2024 ---------------------------------------- - Implemented the arguments "--debug" and "--debug-asn1" in responders to enable debugging mode. Revision 1.1.4, released on Aug 01, 2024 ---------------------------------------- - Added SNMP_ALLOW_ROOT environment variable to allow running SNMP simulator as root. This is a security risk and should be used with caution. Revision 1.1.3, released on Jul 23, 2024 ---------------------------------------- - Fixed notification variation module. Revision 1.1.2, released on Jul 16, 2024 ---------------------------------------- - Removed package postfix. Revision 1.1.1, released on Jun 28, 2024 ---------------------------------------- - Fixed a file path bug. Revision 1.1.0, released on Mar 30, 2024 ---------------------------------------- - Upgraded pysnmp-lextudio to 6.1. Revision 1.0.6, released on Mar 05, 2024 ---------------------------------------- - Locked pysnmp-lextudio to 5.0 to avoid breaking changes in 6.0. Revision 1.0.5, released on Oct 15, 2023 ---------------------------------------- - Fixed IPv6 endpoint support (by kbaikov). Revision 1.0.4, released on Sep 16, 2023 ---------------------------------------- - Merged walk.py patches from inexio. Revision 1.0.3, released on Sep 16, 2023 ---------------------------------------- - Merged other pull requests on upstream. Revision 1.0.2, released on Sep 16, 2023 ---------------------------------------- - Fixed a few bugs in snmpsim-record-mibs. Revision 1.0.1, released on Jan 26, 2023 ---------------------------------------- - Added data and variation folders back to package. Revision 1.0.0, released on Nov 13, 2022 ---------------------------------------- - Command-line tools refactored into entry-points and renamed for better clarity and name spacing for the upcoming tools: * snmpsimd.py -> snmpsim-command-responder * snmprec.py -> snmpsim-record-commands * mib2dev.py -> snmpsim-record-mibs * pcap2dev.py -> snmpsim-record-traffic * datafile.py -> snmpsim-manage-records - Python 2.4-2.6 support has been dropped. - Converted integration tests from depending on @snmplabs.com to local, autonomous test suite. - Replace `getopt` with `argparse` in all CLI tools The latter is way more advanced and Pythonic. Due to differences in `getopt` vs `argparse` usage model, slight changes in tools' behavior is not impossible. Some legacy, backward compatibility CLI options have been dropped by way of this change. - Remove `execfile` use on Py2. - Split monolithic command responder onto many specialized modules and two command responder applications: one implementing fully-fledged SNMPv3 architecture (supporting all SNMP versions) and the other implementing just v1 and v2c. The second one is lighter on the resources. So we now have two daemons: * snmpsim-command-responder * snmpsim-command-responder-lite - Add activity reporting feature On every query, SNMP simulator command responder calls its "reporting" subsystem. If configured, it invokes one of the "modules" responsible for doing something with request information. Included modules ("fulljson" and "minimaljson" reporters) accumulate and periodically dump JSON documents containing request counters scoped by SNMP command processing steps. Future modules may just send out the information to external collectors e.g. Graphite/Carbon or store it in some other format e.g. Prometheus. Revision 0.4.9, released on XX-11-2022 ---------------------------------------- - Changed web site to pysnmp.com. Revision 0.4.8, released on XX-08-2019 ---------------------------------------- - Code base PEP8'ed - Added support for SNMP Context Engine ID mapping to the file system paths. This feature allows for every single SNMP engine to simulate multiple SNMP contexts based on different .snmprec files. On top of that, this feature simplifies the mapping of empty SNMP community and context names to .snmprec files. - Added support for simulation based on compressed .snmprec files using bzip2 compression algorithm (.snmprec.bz2), as well as recording straight into this compressed format. By way of implementing this change, all snmpsim tools have been generalized to variate and record using any of the supported simulation file formats (for as long as chosen format supports variation or recording). - Run snmpsimd variation modules initialization under non-privileged user - MIB resolution failure handling improved in `mib2dev` - Rearranged documentation, removed public SNMP simulator page - Fixed crash caused by pysnmp failing to load up the rest of its MIBs when snmpsimd is switched into non-privileged user - Fixed wrong cache directory and files ownership when switching snmpsimd from privileged into non-privileged user - Fixed .snmpwalk hex string parser - Fixed hex value evaluation error on Python 3 when produced in bytes by a variation modules - Fixed '\\' evaluation in escaped strings (tag 4e) - Fixed `redis` variation module to run under Py3 - Fixed broken indices clean-up, especially on Mac OS - Fixed broken `multiplex` module initialization - Fixed broken NULL value parsing in .snmpwalk files Revision 0.4.7, released on Feb 09, 2019 ---------------------------------------- - Added missing SNMP PDU error-status values to the `error` variation module - Fix to define `PrivilegesOf` on Windows Revision 0.4.6, released on Jan 25, 2019 ---------------------------------------- - The `--log-level` option added - Default daemon syslog service destination to a local socket - Fixed timed log file rotation to respect log file creation time when computing next rotation time - Fixed possible file permission issues by initializing pysnmp modules, logger and variation modules subsystems under reduced user privileges - Improve error reporting in `notification` variation module Revision 0.4.5, released on Dec 30, 2018 ---------------------------------------- - Copyright notice bumped up to year 2019 - Bumped upper Python version to 3.7 - Use absolute paths of Simulator data files and variation modules and reflect that in the log -- that should ease troubleshooting - Optional .snmprec tag modifier is ignored by the *error*, *delay*, *writecache* and *notification* variation modules - Fixed DBM file index detection code to operate correctly - Fixed pysnmp/pyasn1 debugging flags print out in `--help` to work with newer pyasn1/pysnmp Revision 0.4.4, released on Feb 20, 2018 ---------------------------------------- - Fixed regression to non-snmprec format evaluator when called in a non-simulation context (e.g. datafile.py) - Fixed non-functional SQL isolation level setup at the `sql` variation module Revision 0.4.3, released on Feb 12, 2018 ---------------------------------------- - Copyright notice bumped up to year 2018 - Fix to strip labels from enumerations when producing .snmprec file - Fix to SNMP GET command processing by the non-snmprec storage backends - it used to return next OID instead of noSuchObject Revision 0.4.2, released on Dec 22, 2017 ---------------------------------------- - Python literal string representation added as a new flavor of .snmprec string type field (4e) - Migrated references from SourceForge - Integer and enumerations parsing of snmpwalk output improved - Added strong AES privacy algorithms with Blumenthal key localization - Fixed SNMP crypto algorithm identifiers to be named consistently after key length rather than MAC length Revision 0.4.1, released on Oct 29, 2017 ---------------------------------------- - SHA2 authentication protocol suite added. - Sphinx documentation added based on the original web-site user manual - Simulator's SNMPv3 command line option processing hardened to make easier to understand. - Notification variation module now accepts hex strings in var-binds - Fixes to Windows path to log file handling. - Fixes to MIB objects command-line parsing. - Source code PEP8'ed Revision 0.3.1, released on Jan 21, 2017 ---------------------------------------- - Fix to __doc__ use in setup.py to make -O0 installation mode working. - Copyright notes added to source code files. - Per-OID access control added to 'writecache' variation module. - Data file binary search performance improved - Option --string-pool-file for mib2dev.py tool implemented - Default string pool for mib2dev.py tool changed to a pangram - Author's e-mail changed, copyright extended to year 2017 - Fix to PID file creation when running Python 3. - Fix to datafile.py tool that apparently stopped working. - Fix to snmpwalk Timeticks parser - Fix to mib2dev.py stopping prematurely iterating over OIDs in MIB - Fix to accidentally expanded shebang in console scripts (hopefully) Revision 0.3.0, released on Sep 28, 2015 ---------------------------------------- - Simulator now depends on the latest pysnmp features introduced in pysnmp version 4.3.0 - Introducing initial MIB compiler (PySMI) integration. Most command-line tools can now be given MIB symbols rather than OIDs. Requested MIBs will be automatically downloaded from http://mibs.snmplabs.com/ . - New Redis-backed variation module implemented to let users record into and replay simulation data from noSQL key-value store optionally involving server-side Lua scripting. - Full-line comments (#-based) and empty lines are now allowed in data files. - When recording .snmprec data, hex everything if a non-alpha/numeric literal is present in a value. This eliminates possible issues with unescaped contents in a SQL-based recorder. - New --max-varbinds option can now be used for limiting the number of variable bindings in SNMP response message on a per-engine-id basis. Global default is now 64 variable bindings. - Multiple SNMP ContextEngineID can now be configured per each of possibly many SNMP Engine IDs. - SNMP configuration logging reworked for better clarity. - The notification module now binds to the same local interface through which Simulator received CommandRequest triggering notification. Also it supports 'bindaddr' option to override the above behaviour on a per-OID basis. - The sql variation module not switches default SQL transaction isolation level to 'READ COMMITTED'. Isolation level (0-3) could now be specified on per-instance basis through 'isolationlevel' option. - The subprocess variation module improved to expose more SNMP engine parameters to user process. - Switched to the latest pysnmp's requestObserver facility for getting request details from pysnmp core. - Numeric module's 'function' parameter now accepts optional arguments. - The snmprec.py tool improved to optionally survive SNMP PDU-level errors and keep walking remote Agent using an OID derived from the failed one. - Configurable SNMP request timeout and retry count settings now supported by the snmprec.py tool. - The --context-engine-id option support added to snmprec.py tool. - Hex values can now be passed to --v3-context-\* options to snmprec.py tool - Variation modules options separators can now be escaped by doubling or tripling them. - All logging moved to Python logging framework. Some more log targets (such as remote syslog) added. - Low-level SNMP and ASN.1 debugging implemented for all relevant scripts. - Configure both plain-text and hashed versions of snmpCommunityName, contextName whenever its length does not exceed 32 chars. That might ease ContextName usage for Agent addressing. - Wheel distribution format now supported. - Fix to log file autorotation feature. - Fix to pcap2dev.py not to loose the last trailing OID in capture. - Fix to variation module recordContexts isolation to make it dedicated to each Variation Module+Agent instance. - Fix to snmpEngine configuration code at notification.py variation module. - Fixes to --logging-method formatting in --help output. - Fix to numeric.py module value wrapping feature. - Fix to MIB selection code to prevent (and report) LCD access. - Multiple comma-separated debug options now supported. - PostgreSQL is now supported by sql variation module. - Fix to sql.py variation module to work with Python older than 2.5. - Fix to sql.py variation module to avoid 'Unread result found' MySQL error. - The snmpwalk OPAQUE: Float: syntax is now supported. - Fix to HEX value handling in snmpwalk format handler. - Fix to absolute file log path on Windows. - Fix to off-by-one errorIndex as reported by error.py and writecache.py variation modules. - Fix to record parsers/builders to fail on empty values. - Fix to snmprec.py & pcap2dev.py in part of processed OIDs counting. Revision 0.2.4, released on Oct 04, 2013 ---------------------------------------- - A tool for building SNMP Simulator data files from network packet captures added. - Automatic online data file index rebuild on data file timestamp change implemented. - The sql variation module tweaked to be better compliant with Python DB API 2.0 so that it can now work with MySQL out of the box. Module options also reworked to support named DB connect() parameters. - Simulator can now run many independent SNMP engines each with its own set of data files listening at dedicated transport endpoints. At least pysnmp 4.2.5 is required for this feature to work. - Simulator now accepts the --transport-id-offset command-line parameter to specify the initial transport ID instance for each transport domain configured. - Variation module API changed to allow recording module to communicate to its host time of the next probe to occur. - Variation module API changed so that SNMP engine ID is only available in variate() context. This is due to the new multi-engine ID design. - New --args-from-file command-line parameter added to snmpsimd.py to allow a large number of SNMP engines configured to Simulator. The --agent-endpoint\*-list= family of options discontinued in favor of multiple --agent-\*-endpoint options read from args file. - Distribute is gone, switching to setuptools completely. - Default logging destination for all tools is now stderr. - The --version option of snmprec.py renamed into --protocol-version. - New command-line option --pid-file added. - Daemonization under a non-root user now works. - Fixes to time-based log file rotation implementation. - Fixes to numeric variation module. Also, taglist parameter is now defaulted into all numerical types. - Fix to PID file creation on daemonization. - Fixes to stdio binary mode write to work with Python 3. Revision 0.2.3, released on Aug 01, 2013 ---------------------------------------- - Simulator now supports a list of interfaces to listen on through the --agent-endpoint\*-list= family of options. It's intended for simulation a very large pool of devices. - Introducing new command-line utility "datafile.py" designed to manage data files. Features include: merging, splitting, sorting, de-duplicating, conversion between data file formats. - Automatic log file rotation feature implemented. - A number of improvement to the mib2dev.py tool: * Columnar objects for table indices are now automatically populated from index values * Tables are can now be populated with arbitrary number of rows * Hex values can now be given at the prompt using the 0x syntax * Default automatic value ranges for integers are now much smaller to increase a chance of automatic selection. * Values ranges can now be set for each SNMP type separately. * When generating values, make N probes choosing random values for better automation * Produced values are sorted and de-duplicated. * Fix to OID range checking when specified at the command-line. - Help messages made more readable and complete. - Data file search code fixed (not to crash Simulator in corner cases) and simplified. - Variable conflict fixed that broke --v2c-arch option operations. - Fix to OIDs ordering in --v2c-arch GETBULK responder. - Fix subprocess variation module to work with old Python(s). - Source code linted and improved. Revision 0.2.2, released on May 13, 2013 ---------------------------------------- - Multiple USM user entries with potentially different auth&priv settings can now be configured to snmpsimd.py. - Centralized logging facility added. Logging into syslog or file is now supported. - Simulator process daemonization and privileges drop implemented. - More logging added into snmpsimd.py, snmprec.py and variation modules to ease the understanding of their operation. - The --quiet flag of snmprec.py now deprecated in favor of "null" logging method. - Variation modules execution environment extended to provide contexts for three scopes: record, agent and module. This simplifies modules implementation in terms of storing and managing state/configuration information on per-OID/per-Agent and global basis. - The snmprec.py tool now supports DNS names in c/l target parameter. - New 'cumulative' flag added to the numeric.py variation module. - The multiplex module improved to allow .snmprec file selection via SNMP SET. - Fatal exceptions are now fully logged. - Type checking is now performed on SET operation at writecache module. - Fix to community names '/'-normalization at transport address based variation logic. It appeared broken on Windows only since 0.2.1. - Fix to snmprec.py behaviour on missing variation module directory. - Fix to .snmpwalk grammar parser to support Network Address type tag. - Fix to multiplex.py module to let its multiple instances working independently (each for a subtree). - Multiple fixes and re-work of the numeric.py module - Fix to snmprec.py tool to write snmprec data to stderr in binary mode. - Fix to OID search in a .snmprec in case of a subtree configured on the last line of .snmprec file. - Fix to line separator character used in file logger -- now it's platform-dependent. Revision 0.2.1, released on Apr 07, 2013 ---------------------------------------- - WARNING: this release brings some backward incompatibilities in * variation modules names and options * snmpsimd.py community names (in Windows platform) * stock variation modules installation location * sql module OID format stored in database Please read the changes below for more information. - License updated to vanilla BSD 2-Clause (http://opensource.org/licenses/BSD-2-Clause). - Variation modules can now participate in .snmprec production what can be used for capturing additional information about SNMP Agent being snapshotted in .snmprec files. The following changes have been made to the system: * the snmprec.py tool can be passed variation module name. * variation modules can now define the 'record' callable which will be given a chance to influence snmprec record being written. * existing 'process' callable in variation modules renamed into 'variate' for clarity. * variation modules' init() and shutdown() methods accept \*\*context, 'mode' parameter being passed indicating current operation mode. * variation modules running in recording mode can communicate to the upper levels that they either won't produce any data on the current invocation or request another round of SNMP Agent walk. - Variation modules options now take shape of a key-value pairs. This might break backward compatibility with 0.2.0! - The counter.py and gauge.py variation modules merged into a single numeric.py module which also supports INTEGER&TIMETICKS values as well as recording feature. - The involatilecache.py and volatilecache.py modules merged into a single writecache.py module which also supports SET value verification against per-OID access list. - The error.py variation module extended to support variation based on SET value. - The delay.py variation module extended to support variation based on SET value and time of date. - Format of the OID stored in SQL database changed in a backward incompatible manner. - Recording functionality added to the delay.py and numeric.py modules. - The new "multiplex" variation module added to be able to record and replay a sequence of .snmprec files ordered by time. - SNMP GETBULK operation is now supported by snmprec.py. - Redesign of grammar objects -- the new concept is that they are only responsible for basic record layout, fields normalization. and SNMP types resolution. However complex field formatting (such as grammar-specific tag modifiers) is now up to higher-level 'Record' objects. - Simulator-generated community names now uses '/' as path separator regardless of the platform. This allows for unified Simulator view in terms of community and context names across the platforms. - Snapshot files recording now works under Python3. - Install data and variation modules into package root to make easy_install work again. Search these directories at runtime as a last resort - prefer system or home locations as it is more natural to keep changing data there. - Simulator is now more tolerant to duplicate variation modules (just ignores recently found) and catches duplicate data files (also ignores more recent). - On Windows, search both data and variation modules into %PROGRAMFILES% Revision 0.2.0, released on Mar 12, 2013 ---------------------------------------- - Major overhaul aimed at adding value variation features to the Simulator core: * data files may now hold not only terminal OIDs but also OID subtrees * pluggable value variation modules interfaces and basic modules added * write support added through the use of appropriate variation modules * SQL backend for keeping and modifying SNMP snapshots added in form of a value variation module * subprocess execution variation module added what could be used for external process invocation on SNMP request to Simulator * SNMP Notification Originator variation module added what could be used for sending SNMP TRAP/INFORM messages to SNMP entities on SNMP requests to Simulator - SNMP snapshots now being called 'data files' rather than 'device files' which is a legacy term. - Data files and variation modules are now installed into platform-specific directories. - Data files and variation modules are now looked up at several, platform-specific, locations including $HOME/.snmpsim - Simulator data files indices are now created and kept in a dedicated temporary directory which is also configurable though snmpsimd command-line. - Example data files simplified, more native and foreign snapshots added. - Fix to snmprec.py not to record end-of-mib. - Fix to py2exe settings of setup.py Revision 0.1.6, released on Jan 30, 2013 ---------------------------------------- - Net-SNMP's .snmpwalk files created with the "snmpwalk -ObentU" command can now be used by the Simulator directly. - SimpleAgentPro's .sapwalk files support added to Simulator. - Fixes to --start-oid & --stop-oid params to make the working again. - Simulator reports supported device files types on startup. - Shared device files now supported. Snapshots can now be indicated as shared through a symbolic link. Managers can use different credentials to access and modify the same set of Managed Objects. Revision 0.1.5, released on Aug 23, 2012 ---------------------------------------- - Simulator device file selection by a combination of ContextName, Transport ID and source address implemented. - Rudimentary MIB write support added (snmpset now works). Still need to support MIB-defined constraints. - Fix to snmpsimd.py to ignore possibly missing .dbm file on failed index rebuild. - Fix to OID search code what used to always pick the first OID in file while looking for EOL (Python3 only). Revision 0.1.4, released on Jul 25, 2012 ---------------------------------------- - Simulator device file selection by a combination of Community, Transport ID and source address implemented (for --v2c-arch mode). - UDP/IPv6 and UNIX domain socket transports support added to snmpsimd.py and snmprec.py tools. - The snmpsimd.py and snmprec.py tools now support additional SNMPv3 authentication (SHA) and encryption (3DES, AES192, AES256) algorithms provided by pysnmp. - Simulator now supports binding to and listening at multiple local endpoints. The --agent-address & --agent-port options are now obsolete. - The snmprec.py tool Agent address specification syntax unified with that of snmpsimd.py. - Multiple --device-dir c/l options are now supported by snmpsimd.py. - Some runtime diagnostics added to snmprec.py. - The snmpsimd.py tool unconditionally rebuilds existing indices of unknown format. - Device files handling indexing made faster by not calling .tell(). That also required opening files in 'binary' mode to make sure offset calculation will work correctly. - Attempt to open DBM in "fast" and "unsync" modes when building indices to speed-up index generation. - Package meta information updated. - Fix to snmprec.py to make SNMP debugging working again. - Fix to snmprec.py to properly support non-default SNMP context name. - Fix to infinite loop possibly occurring at OID search routine. - Fix to DBM key type (string vs byte issue) - Fix to mib2dev.py MIB path handling. - Fix to index object to make "snmpwalk -c index" work again. - Fix to index object registration in --v2c-arch mode. - Fix to SNMPv2 exception objects translation into SNMPv1 PDU in the --v2c-arch mode Revision 0.1.3, released on Nov 12, 2011 ---------------------------------------- - Initial revision of the mib2dev.py tool. - Some more MIB-originated device files added. Revision 0.1.2, released on Nov 09, 2011 ---------------------------------------- - Some more py2k refactoring (some features did not work otherwise). - Fix to values tags matching at snmpsimd.py (simulator did not work otherwise). - Fixes to sys.exc_info invocation. Revision 0.1.1, released on Nov 06, 2011 ---------------------------------------- - Major overhaul for Python 2.4 -- 3.2 compatibility: + drop explicit long integer type use + map()/filter() replaced with list comprehension + apply() replaced with \*/\*\*args + dictionary operations made 2K/3K compatible + division operator made 2K/3K compatible + exception syntax made 2K/3K compatible + tuple function arguments resolved to scalars to become py3k compatible Revision 0.0.10, released on Dec 31, 2010 ----------------------------------------- - --agent-port handling bug fixed in snmprec.py - --v3-arch command-line flag added to save on large number of [expensive] addV1System() calls. - setuptools dependencies improved Revision 0.0.9, released on Dec 21, 2010 ---------------------------------------- - Simulator now builds a table of loaded device files in a dedicated 'index' context. - Use OS-specific file extension separator for better portability. - Support .db extensions voluntarily added by gdbm on OS X. - Fix to null-typed value read from .dump device file. - Legacy SNMPv1/v2c community string generation policy dropped. - SNMPv3 VACM initialization dropped altogether, as the specialized SMI backend used by Simulator does not implement access control. - Fix to support zero-length device files. - Ignore value syntax errors in device files occurred when responding. - Optionally validate device file data while indexing. - Allow forced device files re-indexing. - Fix to broken IP address serialization code in recorder. Revision 0.0.8, released on Dec 10, 2010 ---------------------------------------- - Initial public release. snmpsim-1.2.2/MANIFEST.in0000644000000000000000000000023513615410400011660 0ustar00include *.txt *.md *.sh recursive-include data *.snmprec *.snmpwalk *.sapwalk *.txt recursive-include variation *.py recursive-include docs *.rst *.py *.svg snmpsim-1.2.2/THANKS.txt0000644000000000000000000000006513615410400011654 0ustar00Simon Crook Yateen Bhagat Florent Brisson Rich Brown snmpsim-1.2.2/TODO.txt0000644000000000000000000000025513615410400011432 0ustar00* Improve .snmprec comments handling in the `datafile.py` tool. Possibly allow dropping/rearranging comments and empty lines * Refactor the documentation into Sphinx form snmpsim-1.2.2/docs/Makefile0000644000000000000000000000114713615410400012515 0ustar00# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = SNMPSimulator SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) snmpsim-1.2.2/docs/source/changelog.rst0000644000000000000000000000006513615410400015034 0ustar00 Changelog ========= .. include:: ../../CHANGES.rst snmpsim-1.2.2/docs/source/conf.py0000644000000000000000000001577013615410400013663 0ustar00#!/usr/bin/env python3 # # SNMP Simulator documentation build configuration file, created by # sphinx-quickstart on Mon Oct 23 17:36:06 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.viewcode", "sphinx_sitemap", "sphinx_copybutton", "notfound.extension", ] notfound_urls_prefix = "/snmpsim/" html_baseurl = "https://docs.lextudio.com/snmpsim/" sitemap_url_scheme = "{link}" sitemap_suffix_included = False # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "SNMP Simulator" copyright = "2010-2019, Ilya Etingof. ТЉ Copyright 2022-2026, LeXtudio Inc." author = "LeXtudio Inc. " # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. release = "1.2.2" # The short X.Y version. version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" pygments_dark_style = "monokai" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { "announcement": 'Try SNMP Studio: Explore SNMP Studio.', "source_repository": "https://github.com/lextudio/snmpsim", "source_branch": "main", "source_directory": "docs/source/", "footer_icons": [ { "name": "GitHub", "url": "https://github.com/lextudio/snmpsim", "html": "", "class": "fa-brands fa-solid fa-github fa-2x", }, ], } html_css_files = [ "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/fontawesome.min.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/solid.min.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/brands.min.css", ] html_title = "SNMP Simulator Documentation" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "_static/favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars # html_sidebars = { # '**': [ # 'relations.html', # needs 'show_related': True theme option to display # 'searchbox.html', # ] # } # If true, links to the reST sources are added to the pages. # html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = False # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = "SNMPSimulatordoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, "SNMPSimulator.tex", "SNMP Simulator Documentation", "Ilya Etingof", "manual", ), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "snmpsimulator", "SNMP Simulator Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "SNMPSimulator", "SNMP Simulator Documentation", author, "SNMPSimulator", "One line description of project.", "Miscellaneous", ), ] def setup(app): on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: """Insert Google Analytics tracker Based on this Stackoverflow suggestion: https://stackoverflow.com/a/41885884 """ app.add_js_file("https://www.googletagmanager.com/gtag/js?id=G-DFLYZZK12P") app.add_js_file("google_analytics_tracker.js") snmpsim-1.2.2/docs/source/development.rst0000644000000000000000000000412013615410400015423 0ustar00 Future features =============== .. toctree:: :maxdepth: 2 We are thinking of further SNMP Simulator development in these particular areas: SNMP credentials via variation modules -------------------------------------- Let variation modules configuring custom SNMPv1/v2c community names and SNMPv3 context names. That would be the alternative way to address simulation data. Complete SNMP table simulation ------------------------------ Simulating SNMP table: while the latest Simulator release supports writable OIDs and SQL backend (which also allows for writable OIDs with potentially complex logic triggered by their access), we think a lightweight variation module readily implementing a RowStatus-driven SNMP table would also be handy. Users of that module would invoke it from their *.snmprec* file for an OID subtree serving their SNMP table. The module would accept basic configuration parameters such as: table columns OIDs and types, valid index ranges, default initialization parameters. Render simulation data from templates ------------------------------------- Allow rendering simulation data from a `template `_ based on user-supplied variables possibly taken from SNMP query, process and host contexts. SNMP notifications simulation ----------------------------- SNMP Notifications: current implementation can fully simulate SNMP Command Generator operations. It would be nice to be able to record SNMP Notifications coming from live SNMP Agents, store and replay them at configured rate and possibly modified (variated) values. Composable simulation data -------------------------- Composable devices: the idea is to be able to compose simulated device from specific MIBs the device implements. Unordered OIDa -------------- Re-work data files index implementation to allow unordered OIDs and better performance on GETNEXT/GETBULK operations. If you need one of these or some other feature - please open a `feature request `_ at GitHub or e-mail directly to *etingof@gmail.com* to discuss contractual work possibilities. snmpsim-1.2.2/docs/source/index.rst0000644000000000000000000000510413615410400014213 0ustar00.. include:: /includes/_links.rst SNMP Simulator ============== Simulation plays a crucial role in the network management tool ecosystem as it allows developers and network administrators to test and validate their applications and systems in a controlled environment. By simulating various SNMP speaking devices, it becomes possible to replicate real-world scenarios and evaluate the behavior of network management tools under different conditions. PySNMP provides a mature and reliable solution for SNMP simulation. With PySNMP's SNMP Simulator tool, users can easily create virtual SNMP devices that mimic the behavior of actual devices on the network. This enables comprehensive testing of SNMP-based applications, including monitoring, configuration management, and performance analysis. The software is free, open source, and immediately available to anyone for whatever purpose, making it an accessible and valuable tool for network management professionals. How to Use SNMP Simulator ------------------------- Try the fast lane if you are fluent with network management matters. .. toctree:: :maxdepth: 2 Return to PySNMP Homepage /quick-start SNMP Simulator Suite -------------------- Detailed documentation explaining the entire work flow and SNMP simulator tool set. For larger-scale, automated deployments REST API based `control plane `_ can be used for centralized management and monitoring purposes. .. toctree:: :maxdepth: 2 /documentation/index Source Code ----------- Project source code is hosted at `GitHub `_. Everyone is welcome to fork and contribute back! We maintain a detailed log of changes: .. toctree:: :maxdepth: 1 /changelog Download -------- The easiest way to download and install SNMP simulator is to ``pip install`` the latest version from PyPI: .. code-block:: bash $ pip install snmpsim Alternatively, you can `download `_ the latest release from GitHub or `PyPI `_. License ------- The SNMP Simulator software is distributed under 2-clause BSD license .. toctree:: :maxdepth: 1 /license Contact ------- In case of questions or troubles using SNMP Simulator, please open up a new `GitHub issue`_ or ask on `Stack Overflow`_. For other inquiries, please contact `LeXtudio Inc.`_. More information about support options can be found in the following section. .. toctree:: :maxdepth: 1 Support Options snmpsim-1.2.2/docs/source/license.rst0000644000000000000000000000007713615410400014532 0ustar00 .. _license: License ======= .. include:: ../../LICENSE.txt snmpsim-1.2.2/docs/source/quick-start.rst0000644000000000000000000001467413615410400015367 0ustar00.. include:: /includes/_links.rst Quick Start =========== .. toctree:: :maxdepth: 2 Installation ------------ SNMP Simulator is written in Python and depends on other Python libraries. The easiest way to deploy SNMP Simulator is by downloading it from PyPI. Below we quickly set up a Python 3.12 virtual environment and install SNMP Simulator into it. .. code-block:: bash $ pyenv local 3.12 $ pip install pipenv $ pipenv --python 3.12 $ pipenv install snmpsim .. note:: You might want to use some existing data files to get started quickly. Then, you can install ``snmpsim-data`` package as well. You can learn more about it in the `SNMP Simulator Data`_. Run SNMP Simulator ------------------ Once installed, invoke ``snmpsim-command-responder`` daemon and point it to a directory containing simulation data: .. code-block:: bash $ pipenv run snmpsim-command-responder --data-dir=./data/UPS \ --agent-udpv4-endpoint=127.0.0.1:1611 This command starts the Simulator on UDP/IPv4 port 1611. It will respond to SNMP queries with simulation data stored in ``./data/UPS`` directory. Test the Setup -------------- Depending on how many data files are loaded, the Simulator initializes a number of agents. You can then try them out with Net-SNMP's command-line tools which are usually shipped along with your operating system: .. code-block:: bash $ snmpwalk -v2c -c apc-8932 127.0.0.1:1611 system SNMPv2-MIB::sysDescr.0 = STRING: APC Web/SNMP Management Card (MB:v4.1.0 PF:v6.7.2 PN:apc_hw05_aos_672.bin AF1:v6.7.2 AN1:apc_hw05_rpdu2g_672.bin MN:AP8932 HR:02 SN: 3F503A169043 MD:01/23/2019) SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::enterprises.318.1.3.4.6 DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (165328680) 19 days, 3:14:46.80 SNMPv2-MIB::sysContact.0 = STRING: Unknown SNMPv2-MIB::sysName.0 = STRING: pwr-dc01-pdu-rack3-01 SNMPv2-MIB::sysLocation.0 = STRING: Unknown SNMPv2-MIB::sysServices.0 = INTEGER: 72 SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00 SNMPv2-MIB::sysORID.1 = OID: SNMPv2-MIB::snmpMIB SNMPv2-MIB::sysORID.2 = OID: SNMP-FRAMEWORK-MIB::snmpFrameworkMIBCompliance SNMPv2-MIB::sysORID.3 = OID: SNMP-MPD-MIB::snmpMPDCompliance SNMPv2-MIB::sysORID.4 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance SNMPv2-MIB::sysORID.5 = OID: SNMP-VIEW-BASED-ACM-MIB::vacmMIBCompliance SNMPv2-MIB::sysORDescr.1 = STRING: The MIB Module from SNMPv2 entities SNMPv2-MIB::sysORDescr.2 = STRING: SNMP Management Architecture MIB SNMPv2-MIB::sysORDescr.3 = STRING: Message Processing and Dispatching MIB SNMPv2-MIB::sysORDescr.4 = STRING: USM User MIB SNMPv2-MIB::sysORDescr.5 = STRING: VACM MIB SNMPv2-MIB::sysORUpTime.1 = Timeticks: (0) 0:00:00.00 SNMPv2-MIB::sysORUpTime.2 = Timeticks: (0) 0:00:00.00 SNMPv2-MIB::sysORUpTime.3 = Timeticks: (0) 0:00:00.00 SNMPv2-MIB::sysORUpTime.4 = Timeticks: (0) 0:00:00.00 SNMPv2-MIB::sysORUpTime.5 = Timeticks: (0) 0:00:00.00 Simulation data are simple plain-text file, with ``.snmprec`` (or other) file extensions. Each line in represents a single SNMP object in form of pipe-separated fields ``OID|TYPE|VALUE``. .. code-block:: bash $ cat ./data/UPS/apc-8932.snmprec 1.3.6.1.2.1.1.1.0|4x|415043205765622f534e4d50204d616e6167656d656e74204361726420284d423a76342e312e302050463a76362e372e3220504e3a6170635f687730355f616f735f3637322e62696e204146313a76362e372e3220414e313a6170635f687730355f7270647532675f3637322e62696e204d4e3a4150383933322048523a303220534e3a20334635303341313639303433204d443a30312f32332f3230313929 1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.318.1.3.4.6 1.3.6.1.2.1.1.3.0|67|165328680 1.3.6.1.2.1.1.4.0|4|Unknown 1.3.6.1.2.1.1.5.0|4x|7077722d646330312d7064752d7261636b332d3031 1.3.6.1.2.1.1.6.0|4|Unknown 1.3.6.1.2.1.1.7.0|2|72 1.3.6.1.2.1.1.8.0|67|0 1.3.6.1.2.1.1.9.1.2.1|6|1.3.6.1.6.3.1 1.3.6.1.2.1.1.9.1.2.2|6|1.3.6.1.6.3.10.3.1.1 1.3.6.1.2.1.1.9.1.2.3|6|1.3.6.1.6.3.11.3.1.1 1.3.6.1.2.1.1.9.1.2.4|6|1.3.6.1.6.3.15.2.1.1 1.3.6.1.2.1.1.9.1.2.5|6|1.3.6.1.6.3.16.2.1.1 1.3.6.1.2.1.1.9.1.3.1|4x|546865204d4942204d6f64756c652066726f6d20534e4d50763220656e746974696573 1.3.6.1.2.1.1.9.1.3.2|4x|534e4d50204d616e6167656d656e7420417263686974656374757265204d4942 1.3.6.1.2.1.1.9.1.3.3|4x|4d6573736167652050726f63657373696e6720616e64204469737061746368696e67204d4942 1.3.6.1.2.1.1.9.1.3.4|4x|55534d2055736572204d4942 1.3.6.1.2.1.1.9.1.3.5|4x|5641434d204d4942 1.3.6.1.2.1.1.9.1.4.1|67|0 1.3.6.1.2.1.1.9.1.4.2|67|0 1.3.6.1.2.1.1.9.1.4.3|67|0 1.3.6.1.2.1.1.9.1.4.4|67|0 1.3.6.1.2.1.1.9.1.4.5|67|0 ... The Simulator analyzes the parameters (such as SNMP community name or SNMPv3 context name and/or IP address) of SNMP query to determine which agent (whose data from a specific ``.snmprec`` file) to respond with. Simulate Existing SNMP Agent ---------------------------- Besides creating simulation data by hand, you can generate it from some existing SNMP agent. Here we use publicly available SNMP Simulator instance as a donor device: .. code-block:: bash $ pipenv run snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --output-file=./data/public.snmprec SNMP version 2c, Community name: public Querying UDP/IPv4 agent at 195.218.195.228:161 Agent response timeout: 3.00 secs, retries: 3 Sending initial GETNEXT request for 1.3.6 (stop at ).... OIDs dumped: 182, elapsed: 11.97 sec, rate: 7.00 OIDs/sec, errors: 0 .. note:: We host many simulation data files in ``snmpsim-data`` package. You can learn more about them in the `SNMP Simulator Data`_. Simulate from MIB ----------------- Alternatively, you could build simulation data from a MIB file: .. code-block:: bash $ pipenv run snmpsim-record-mibs --output-file=./data/public.snmprec \ --mib-module=IF-MIB # MIB module: IF-MIB, from the beginning till the end # Starting table IF-MIB::ifTable (1.3.6.1.2.1.2.2) # Synthesizing row #1 of table 1.3.6.1.2.1.2.2.1 ... # Finished table 1.3.6.1.2.1.2.2.1 (10 rows) # End of IF-MIB, 177 OID(s) dumped You can even sniff network traffic on the wire recovering SNMP messages there and building simulation data from it. Besides static files, SNMP simulator can be configured to call its plugin modules for simulation data. We ship plugins to interface SQL and NOSQL databases, file-based key-value stores and other sources of information. Related Resources ----------------- - `Support Options`_ - :doc:`/documentation/index` - :doc:`/license` snmpsim-1.2.2/docs/source/_static/favicon.ico0000644000000000000000000000767613615410400016141 0ustar00  Ј(@ УУџџџџџџџџџџџџџџџџџџџџџџџџ§§§џљљљџєєєџ№№№џыыыџщщщџшшшџщщщџэээџёёёџіііџћћћџўўўџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџћћћџяяяџмммџЦЦЦџЏЏЏџ››œџ‹ŒŒџ€‚џz{|џyyzџ||}џ„„…џ‘џЁЁЁџЖЖЖџЭЭЭџуууџєєєџ§§§џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџќќќџшшшџНННџ‹‹‹џ__`џ>?@џ,,,џ'% џ($џ+%џ+%џ*#џ&!џ"џ џ$#"џ/01џFGHџjklџšššџЬЬЬџёёёџўўўџџџџџџџџџџџџџџџџџџџџџџџџџќќќџлллџ‰‰‰џ>?@џџ"џC3џmRџl%џЇ~.џДˆ3џЙŽ7џИŽ8џГ‹8џЇ‚4џ•t.џ}a%џ^Iџ>0џ' џ'%"џQQRџЄЄЅџыыыџџџџџџџџџџџџџџџџџџџџџџџџџ№№№џ˜™™џ))(џ& џeMџЂz'џЫ˜4џрЈ=џщЏCџьДGџюЖJџюИMџяКPџюЛRџюМTџыЛUџчЙTџнБNџЫЁCџЈ‚-џuWџ>/џHFAџНННџќќќџџџџџџџџџџџџџџџџџџџџџэээџ‹Š‡џO@џ–r"џС‘.џй 4џуЇ9џфЉ<џфЋ?џфЌAџфЎDџфЏFџфБIџфВLџхДNџфЕQџфЗTџфИVџуИWџкЏLџХ–1џЅzџnUџЅЃžџћћћџџџџџџџџџџџџџџџџџџџџџљњњџЙБ™џ•r$џГ†&џЧ‘*џз›/џи1џиž4џи 7џиЁ9џиЃ<џкЅ>џпЇ@џиЈDџиЉGџиЋIџиЌLџиЎOџиАRџдЌNџХ—3џИ‰$џšt џЙГЂџўўџџџџџџџџџџџџџџџџџџџџџџџџџџЮФЅџlџІ{џОˆ#џЬ‘'џЬ’*џЬ”-џЬ•/џЬ—2џЬ˜5џгœ7џтЂ9џЬ<џЭŸ?џЬ BџЬЂEџЭЃGџЬЅJџЧ DџЗ‹)џЌџœz/џдбЦџџџџџџџџџџџџџџџџџџџџџџџџџџџџџщхгџ‹q2џ‹gџЎ|џФ‰"џУ‰"џТ‹&џТ)џТŽ+џС.џЫ•1џнž6џТ”6џТ•8џС—;џС˜=џТš@џТEџК“;џЅzџ˜nџЄŽYџяяьџџџџџџџџџџџџџџџџџџџџџџџџџџџџџўўљџДЄyџxZџšnџв-џкЂJџЯ”2џЩ‘.џЦ/џФ‘1џЬ•3џмž6џУ•9џУ˜>џХœDџШЂMџЭЋ[џаГhџГ’Dџ”lџ’nџЧПІџўўџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџьчжџŽv=џƒ`џШŒ5џљпНџєФ‚џьЈEџшЊKџфЋPџфЋNџшЊFџсДbџтКlџфСwџчЩ„џъб’џйТ‚џЃ3џgџЌ–^џёёыџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџўџбЧЈџd#џ r#џэаЇџўыбџіГWџѕГUџєИ_џёЖ[џюАNџёФwџ№Щ€џяЮˆџяв‘џъг”џСЅ`џ”nџp&џЖБœџбвдџбвгџзийџффхџѓѓѓџ§§§џџџџџџџџџџџџџџџџџџџџџќќѕџЕЅyџ…aџЧ›VџћщбџїМkџєВTџєИ_џ№Ж[џэАMџёФwџ№Щ€џяЭˆџюв‘џмТ€џІ„7џˆcџZI!џVN;џYO:џVM9џSL=џVRJџiheџ–—˜џиийџќќќџџџџџџџџџџџџџџџџџђ№тџ†Oџ›p"џу­_џіДXџєГUџєИ_џ№Ж[џьЏMџ№Фwџ№Щ€џяЮˆџъЭ‹џРЁZџ“nџЂw џЕˆ0џЫ™:џгЂAџвЃDџЩžDџЕ>џ’s0џdO!џgaSџбббџџџџџџџџџџџџџџџџџџџџџскТџ“u3џН…/џ№ЋKџѕГVџєИ_џ№ЕZџыЎLџ№Фwџ№Щ€џяЭˆџйКuџЁ~1џžsџдš0џсІ:џсЉ>џрЋCџсЎHџсБMџсДRџпДSџЧ›8џ™v+џЫЦЛџџџџџџџџџџџџџџџџџџџџџџџќџЬРœџœu+џкBџєГUџєЗ]џяЕZџъЎLџ№Фwџ№Щ€џщЦ€џЙ—MџŽiџЊ{џЪ&џЪ’+џЪ•0џЩ—5џЪš9џЪ>џЪŸCџЩЂGџИ.џБ>џ№ьтџџџџџџџџџџџџџџџџџџџџџџџџџћћѓџМЉyџД„1џьБZџїШ‚џяЗ]џщЎKџёФwџяШџбЎeџЂ€3џЄŒJџ”lџб™Aџд@џЭ•4џЪ—9џЩ›@џЪ IџЮЊXџЪЋ^џŸy$џХА‚џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџєђфџИœ`џа SџћъаџђЦџш­IџёФwџфНrџАŒ?џЦГuџёюлџЂ‰WџХž`џља™џёАRџюЖ_џьОmџьЦ}џьаŽџХЈbџЅ…Aџ№ырџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџэчвџРžZџыг­џѕеЂџшЌIџэСsџЦŸSџОІdџїѕрџџџџџтмЮџЇ†HџшИtџіЕYџѓЛeџђУtџёЬ„џуХџЊ‰?џиЪ­џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџчнОџвЌhџэНqџч­JџоВdџНžYџёьбџџџџџџџџџџџџџЯТІџС:џђБTџѓЛdџёУtџяЪ‚џУЂYџУЏ‚џ§ќћџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџљїьџЮЊfџц­Qџч­JџдЈYџЧАtџўўіџџџџџџџџџџџџџќћњџЪВ†џоЋ^џѕЧ~џђУtџнЖkџПІoџіѓэџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџјіъџУЂaџЯšEџб™<џО“HџЪЖ~џџџїџџџџџџџџџџџџџџџџџіѓэџиО‘џђаšџэОoџЬЌnџюшлџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџљљ№џШЛœџПiџОŽEџЋ’iџЫТžџџџјџџџџџџџџџџџџџџџџџџџџџюуЯџтГgџпБ`џоЮ­џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџљљ№џггЦџйЬ­џзГoџЗЗЇџЬЫАџџџјџџџџџџџџџџџџџџџџџџџџџ№ытџЫЊyџС mџсйЩџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџљљ№џввФџиЫЌџзГnџЗЖІџЭЫАџџџљџџџџџџџџџџџџџџџџџџџџџєєєџнмнџЭЫЭџттуџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџєѕэџежЫџзЮЖџдЙƒџУУЙџггНџўўљџџџџџџџџџў§ћџџџўџџџџџщъъџаЮЪџЬЩУџстуџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџћћћџзизџЇЈЋџЄšŠџОЂmџВГЕџЯаЮџюяюџў§њџюйВџчЧŒџщЭ™џљђцџчщьџЬМŸџгН•џщъьџўўўџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџўўўџьэьџезиџбЧЕџзКƒџкккџуффџіїјџєцЬџщЭ—џљђфџ№оЛџыб џџџџџѓфЧџюиЏџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџіэмџъаŸџ§§ќџџџџџџџџџямЖџєцЭџџџџџќљѓџыаŸџ§ћјџєчЭџ№нЙџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџљђхџюи­џџў§џџџџџџџџџђуХџѕъдџџџџџќњѕџялДџ§ќњџѕщвџєчЮџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ§ќљџѕъгџћјёџўў§џџџџџњѕыџіюлџ§ќљџљєшџіюмџќћїџїюмџћјёџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџћјёџіэйџіэкџіюлџѕэйџєшЮџѕыгџѕъвџѕыжџіэкџљєшџџўўџџџџџџџџџџџџџџџџџџџџџџџџџsnmpsim-1.2.2/docs/source/_static/google_analytics_tracker.js0000644000000000000000000000021713615410400021374 0ustar00window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-DFLYZZK12P'); snmpsim-1.2.2/docs/source/_static/logo.svg0000644000000000000000000003276313615410400015474 0ustar00 snmpsim-1.2.2/docs/source/_templates/page.html0000644000000000000000000005021213615410400016311 0ustar00{% extends "!page.html" %} {% block right_sidebar %}
Advertisement
{{ super() }} {% endblock right_sidebar %} {% block footer %} {{ super() }}
Advertisement
{% endblock footer %} snmpsim-1.2.2/docs/source/documentation/addressing-agents.rst0000644000000000000000000001610213615410400021357 0ustar00 .. _addressing-simulation-data: Addressing SNMP agents ====================== Sometimes SNMP managers can't easily change community name to address particular simulated device instance as mention in the previous section. Or it may be useful to present the same simulated device to different SNMP managers differently. .. _lite-mode-variation: Lite mode --------- When running lite command responder, SNMP Simulator is not using SNMPv3 framework. That implies that SNMPv3 infrastructure can't be used for agent addressing. In this mode, only SNMP v1 and v2c versions can be handled. The main reason for this lite/legacy mode support is higher performance. In lite mode, SNMP Simulator attempts to find a *.snmprec* file to fulfill the request by probing files by paths constructed from pieces of SNMPv1/v2c request data. Path construction occurs by these rules and in this order: 1. *self / / / * .snmprec 2. *self / / * .snmprec 3. *self / * .snmprec 4. *self* .snmprec The *self* component is just a constant which conceptually refers to the SNMP engine serving current request. One of the use-cases is to serve requests with empty SNMP community name. In other words, SNMP Simulator first tries to take community name, destination and source addresses into account. If that does not match any existing file, the next probe would use community name and destination address. The last resort is to probe files by just community name, as described in previous chapters. Transport ID is an OID that also identifies local transport endpoint (e.g. protocol, local address and port Simulator is listening on). It is reported by the Simulator on startup for each endpoint it is listening on. .. code-block:: bash $ snmpsim-command-responder-lite \ --agent-udpv4-endpoint=127.0.0.1:1024 \ --agent-udpv6-endpoint='[::1]:1161' ... Listening at: UDP/IPv4 endpoint 127.0.0.1:1024, transport ID 1.3.6.1.6.1.1.0 UDP/IPv6 endpoint ::1:1161, transport ID 1.3.6.1.2.1.100.1.2.0 When mapping source-address into a file, the following transformation rules apply: * UDP/IPv4: 192.168.1.1 remains 192.168.1.1 * UDP/IPv6: fe80::12e:410f:40d1:2d13 becomes fe80__12e_410f_40d1_2d13 For example, to make Simulator reporting from particular file to a Manager at 192.168.1.10 whenever community name "public" is used and queries are sent to Simulator over UDP/IPv4 to 192.168.1.1 interface (which is reported by Simulator under transport ID 1.3.6.1.6.1.1.0), device file *self/public/1.3.6.1.6.1.1.0/192.168.1.10.snmprec* would be used for building responses. .. _full-mode-variation: SNMPv3 mode ----------- When using full Simulator, SNMPv3 framework is used and all SNMP versions (1, 2c, 3) can be handled. The same filesystem mapping rules apply to SNMP community name, but also to SNMPv3 context name. The path to .snmprec file for fulfilling response is probed at these locations in the following order: 1. *context-engine-id / context-name / transport-ID / source-address* .snmprec 2. *context-engine-id / context-name / transport-ID* .snmprec 3. *context-engine-id / context-name* .snmprec 4. *context-engine-id* .snmprec The *context-engine-id* component is taken from SNMP Context Engine ID field of the SNMP command request. If it happens to be equal to local SNMP engine ID value, then the constant literal *self* will be looked up on the file system instead. Conceptually, *self* refers to the SNMP engine serving current request. One of the side-effects of supporting *context-engine-id* is to serve requests with empty SNMP context/community name (i.e. *self.snmprec*). For example, to make Simulator reporting from particular file to a Manager at 192.168.1.10 whenever context-name is an empty string and queries are sent to Simulator over UDP/IPv4 to 192.168.1.1 interface (which is reported by Simulator under transport ID 1.3.6.1.6.1.1.0), device file *self/1.3.6.1.6.1.1.0/192.168.1.10.snmprec* would be used for building responses. .. _sharing-snmprec-files: Sharing .snmprec files ---------------------- If a symbolic link is used as a data file, it would serve as an alternative CommunityName/ContextName for the Managed Objects collection read from the snapshot file being pointed to: .. code-block:: bash $ ls -l public.snmprec -rw-r--r-- 1 root users 8036 Mar 12 23:26 public.snmprec $ ln -s public.snmprec private.snmprec $ ls -l private.snmprec lrwxrwxrwx 1 root users 14 Apr 5 20:58 private.snmprec -> public.snmprec Shared device files are mentioned explicitly on *snmpsim-command-responder* startup: .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:1161 Scanning "/home/root/.snmpsim/variation" directory for variation modules... no directory Scanning "/usr/local/share/snmpsim/variation" directory for variation modules... 8 more modules found Initializing variation modules: notification... OK sql... FAILED: database type not specified numeric... OK subprocess... OK delay... OK multiplex... OK error... OK writecache... OK Scanning "/usr/local/share/snmpsim/data" directory for *.snmpwalk, *.MVC, *.sapwalk, *.snmprec, *.dump data files... ================================================================== Data file /usr/local/share/snmpsim/data/public.snmprec, dbhash-indexed, closed SNMPv1/2c community name: public SNMPv3 context name: 4c9184f37cff01bcdc32dc486ec36961 -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Shared data file data/public.snmprec, dbhash-indexed, closed SNMPv1/2c community name: private SNMPv3 context name: 2c17c6393771ee3048ae34d6b380c5ec -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ... SNMPv3 credentials: Username: simulator Authentication key: auctoritas Authentication protocol: MD5 Encryption (privacy) key: privatus Encryption protocol: DES Listening at: UDP/IPv4 endpoint 127.0.0.1:1161, transport ID 1.3.6.1.6.1.1.0 Now Managers can then use different credentials to access and modify the same set of Managed Objects. .. code-block:: bash $ snmpwalk -On -v2c -c public localhost:1161 1.3.6 .1.3.6.1.2.1.1.1.0 = STRING: Device description .1.3.6.1.2.1.1.2.0 = OID: .1.3.6.1.4.1.34547 .1.3.6.1.2.1.1.3.0 = Timeticks: (78171676) 9 days, 1:08:36.76 .1.3.6.1.2.1.1.4.0 = STRING: The Owner .1.3.6.1.2.1.1.5.0 = STRING: DEVICE-192.168.1.1 .1.3.6.1.2.1.1.6.0 = STRING: TheCloud .1.3.6.1.2.1.1.7.0 = INTEGER: 72 ... $ snmpwalk -On -v2c -c private localhost:1161 1.3.6 .1.3.6.1.2.1.1.1.0 = STRING: Device description .1.3.6.1.2.1.1.2.0 = OID: .1.3.6.1.4.1.34547 .1.3.6.1.2.1.1.3.0 = Timeticks: (78171676) 9 days, 1:08:36.76 .1.3.6.1.2.1.1.4.0 = STRING: The Owner .1.3.6.1.2.1.1.5.0 = STRING: DEVICE-192.168.1.1 .1.3.6.1.2.1.1.6.0 = STRING: TheCloud .1.3.6.1.2.1.1.7.0 = INTEGER: 72 ... Obviously, *snmpwalk* output is exactly the same for different community names being used. snmpsim-1.2.2/docs/source/documentation/building-simulation-data.rst0000644000000000000000000004332013615410400022645 0ustar00 .. _building-simulation-data: Building simulation data ======================== Simulation data can be put together by :ref:`SNMP walking donor agent `, :ref:`wire-tapping SNMP traffic ` or :ref:`generating from MIBs `. Regular output from :ref:`Net-SNMP snmpwalk ` or :ref:`Agent Pro ` tools can be used as-is. Finally, *.snmprec* files can be created by hand in a text editor or automatically generated by some in-house script based on whatever data you got. Walking SNMP agent ------------------ One way to obtain simulation data is to snapshot some existing SNMP agent by running the *snmpsim-record-commands* tool against your prototype SNMP device. This tool will execute a series of SNMP GETNEXT queries for a specified range of OIDs over a chosen SNMP protocol version and store response data in a .snmprec file. .. _snmpsim-record-commands: The donor SNMP agent recording session would look like this: .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=192.168.1.1 \ --start-oid=1.3.6.1.2.1 --stop-oid=1.3.6.1.2.1.5 \ --output-file=snmpsim/data/recorded/linksys- \ system.snmprec Scanning "/usr/local/share/snmpsim/variation" directory for variation modules... none requested SNMP version 2c Community name: public Querying UDP/IPv4 agent at 192.168.1.1:161 Sending initial GETNEXT request.... OIDs dumped: 304, elapsed: 1.94 sec, rate: 157.00 OIDs/sec $ $ ls -l data/recorded/linksys-system.snmprec -rw-r--r-- 1 root users 16252 Oct 26 14:49 data/recorded/linksys-system.snmprec $ $ head data/recorded/linksys-system.snmprec 1.3.6.1.2.1.1.1.0|4|BEFSX41 1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.3955.1.1 1.3.6.1.2.1.1.3.0|67|638239 1.3.6.1.2.1.1.4.0|4|Linksys 1.3.6.1.2.1.1.5.0|4|isp-gw 1.3.6.1.2.1.1.6.0|4|4, Petersburger strasse, Berlin, Germany 1.3.6.1.2.1.1.8.0|67|4 The *snmpsim-record-commands* tool can run over SNMPv1, v2c and v3. To configure SNMPv3 USM user authentication and privacy algorithms, :ref:`follow the instructions ` for *snmpsim-command-responder*. No special requirements exist for device file name and location. Just keep in mind that at simulation time, *snmpsim-command-responder* treats *.snmprec* file path as SNMPv1/v2c community name or SNMPv3 context name. If you don't readily have some SNMP agent to play with, you're welcome to use our publicly available `SNMP Simulator instance `_. .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --community=public SNMP version 2c, Community name: public Querying UDP/IPv4 agent at 195.218.195.228:161 Sending initial GETNEXT request.... 1.3.6.1.2.1.1.1.0|4|SunOS zeus.pysnmp.com 4.1.3_U1 1 sun4m 1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.20408 1.3.6.1.2.1.1.3.0|67|137765775 1.3.6.1.2.1.1.4.0|4|LeXtudio Inc., support@lextudio.com 1.3.6.1.2.1.1.5.0|4|zeus.pysnmp.com 1.3.6.1.2.1.1.6.0|4|Toronto, Ontario, Canada ... 1.3.6.1.2.1.11.31.0|65|0 1.3.6.1.2.1.11.32.0|65|0 OIDs dumped: 86, elapsed: 2.00 sec, rate: 42.00 OIDs/sec .. note:: For better performance, consider using *GETBULK* SNMP command by passing *--use-getbulk* option to the *snmpsim-record-commands* tool. Faster recording may deliver more consistent SNMP objects state. Since *.snmprec* is a plain text file, you can always edit it in your text editor. For mass changes consider using the :ref:`snmpsim-manage-records` tool. .. _snmpsim-record-mibs: MIB-based synthesis ------------------- The other way to produce simulation data is to run the *snmpsim-record-mibs* tool against virtually any MIB file. With that method you do not have to have a donor device and the values, that would otherwise be reported by the donor SNMP agent, will instead be chosen randomly. Keep in mind that you may run into either of two issues with these randomly chosen values: * Some MIB data suggest certain correlation between formally unrelated pieces of information. Such relationships may be described informally, e.g. in natural language in the Description field. The automated values generation procedure has no chance to assure proper correlations, in that case the overall snapshot may appear inconsistent. * Some data types specified in the MIB may impose certain restrictions on the type instance values. For example an integer-typed Managed Object may be allowed to be either 0 or 12. If a guessed value turns out to be 2, it will be incompatible with this type. While it is possible to introspect type objects and generate a compliant value, the *snmpsim-record-mibs* tool does not do that [yet]. A non-compliant value will result an exception on MIB node instantiation. In that case the *snmpsim-record-mibs* tool will revert to an interactive mode and ask you for a compliant value. * When building snapshots from MIBs you are not simulating the actual values the SNMP agent is reporting. With MIB-based simulation you can basically simulate the collection of OIDs, not the dependencies between them or their interplay. On the bright side, the *snmpsim-record-mibs* tool will respect Managed Object type (e.g type associated with the OIDs), and produce valid indices for the MIB tables. Examples ++++++++ Here we produce simulation data for a portion of OID space of SNMPv2-MIB: .. code-block:: bash $ snmpsim-record-mibs --mib-module=SNMPv2-MIB --start-oid=1.3.6.1.2.1.1.1 \ --stop-oid=1.3.6.1.2.1.1.8 # MIB module: SNMPv2-MIB 1.3.6.1.2.1.1.1.0|4|Portez ce vieux 1.3.6.1.2.1.1.2.0|6|1.3.6.1.3.39.232.14.10.84.109.1 1.3.6.1.2.1.1.3.0|67|350728093 1.3.6.1.2.1.1.4.0|4|whisky 1.3.6.1.2.1.1.5.0|4| 1.3.6.1.2.1.1.6.0|4|whisky au juge blond 1.3.6.1.2.1.1.7.0|2|4 1.3.6.1.2.1.1.8.0|67|3138976393 # End of SNMPv2-MIB, 8 OID(s) dumped The *snmpsim-record-mibs* tool can also generate values for SNMP conceptual tables. It's doing that by iterating over table definition in MIB for specified number of times. The following command will analyze given MIB and produce two rows for the *IF-MIB::ifTable* table: .. code-block:: bash $ snmpsim-record-mibs --mib-module=IF-MIB --start-oid=1.3.6.1.2.1.2.2 \ --stop-oid=1.3.6.1.2.1.2.3 --table-size=2 # MIB module: IF-MIB # Starting table IF-MIB::ifTable (1.3.6.1.2.1.2.2) # Synthesizing row #1 of table 1.3.6.1.2.1.2.2.1 # Finished table 1.3.6.1.2.1.2.2.1 (2 rows) 1.3.6.1.2.1.2.2.1.1.12|2|12 1.3.6.1.2.1.2.2.1.1.26|2|26 1.3.6.1.2.1.2.2.1.2.12|4|vieux whisky 1.3.6.1.2.1.2.2.1.2.26|4|ce vieux whisky au juge 1.3.6.1.2.1.2.2.1.3.12|2|29 1.3.6.1.2.1.2.2.1.3.26|2|1 1.3.6.1.2.1.2.2.1.4.12|2|28 1.3.6.1.2.1.2.2.1.4.26|2|16 1.3.6.1.2.1.2.2.1.5.12|66|3029607807 1.3.6.1.2.1.2.2.1.5.26|66|3150811331 1.3.6.1.2.1.2.2.1.6.12|4| 1.3.6.1.2.1.2.2.1.6.26|4| 1.3.6.1.2.1.2.2.1.7.12|2|1 1.3.6.1.2.1.2.2.1.7.26|2|1 1.3.6.1.2.1.2.2.1.8.12|2|6 1.3.6.1.2.1.2.2.1.8.26|2|5 1.3.6.1.2.1.2.2.1.9.12|67|2871454194 1.3.6.1.2.1.2.2.1.9.26|67|496156868 1.3.6.1.2.1.2.2.1.10.12|65|1488410552 1.3.6.1.2.1.2.2.1.10.26|65|3473823260 1.3.6.1.2.1.2.2.1.11.12|65|1727276906 1.3.6.1.2.1.2.2.1.11.26|65|342963679 1.3.6.1.2.1.2.2.1.12.12|65|1511248359 1.3.6.1.2.1.2.2.1.12.26|65|2207653511 1.3.6.1.2.1.2.2.1.13.12|65|4226165132 1.3.6.1.2.1.2.2.1.13.26|65|36536957 1.3.6.1.2.1.2.2.1.14.12|65|130591184 1.3.6.1.2.1.2.2.1.14.26|65|1852726355 1.3.6.1.2.1.2.2.1.15.12|65|3301920138 1.3.6.1.2.1.2.2.1.15.26|65|470729731 1.3.6.1.2.1.2.2.1.16.12|65|4148984503 1.3.6.1.2.1.2.2.1.16.26|65|953020685 1.3.6.1.2.1.2.2.1.17.12|65|1569764479 1.3.6.1.2.1.2.2.1.17.26|65|2095562772 1.3.6.1.2.1.2.2.1.18.12|65|238446444 1.3.6.1.2.1.2.2.1.18.26|65|3268308217 1.3.6.1.2.1.2.2.1.19.12|65|3230500934 1.3.6.1.2.1.2.2.1.19.26|65|566234076 1.3.6.1.2.1.2.2.1.20.12|65|3549197996 1.3.6.1.2.1.2.2.1.20.26|65|2834484035 1.3.6.1.2.1.2.2.1.21.12|66|68812076 1.3.6.1.2.1.2.2.1.21.26|66|1903146216 1.3.6.1.2.1.2.2.1.22.12|6|1.3.6.1.3 1.3.6.1.2.1.2.2.1.22.26|6|1.3.6.1.3.231.101.247.88 # End of IF-MIB, 44 OID(s) dumped The range of values for automatic and random selection can be controlled on a per-type basis with the *--counter-range*, *--counter64-range*, *--gauge-range*, *--timeticks-range*, *--unsigned-range*, *--integer32-range* options. Words for strings generations can be passed via *--string-pool* option. .. code-block:: bash $ snmpsim-record-mibs --mib-module=UDP-MIB --table-size=1 \ --counter-range=0,100 --unsigned-range=100,200 # MIB module: UDP-MIB # Starting table UDP-MIB::udpTable (1.3.6.1.2.1.7.5) # Finished table 1.3.6.1.2.1.7.5.1 (1 rows) # Starting table UDP-MIB::udpEndpointTable (1.3.6.1.2.1.7.7) # Finished table 1.3.6.1.2.1.7.7.1 (1 rows) 1.3.6.1.2.1.7.1.0|65|66 1.3.6.1.2.1.7.2.0|65|49 1.3.6.1.2.1.7.3.0|65|91 1.3.6.1.2.1.7.4.0|65|14 1.3.6.1.2.1.7.5.1.1.169.148.104.225.14|64x|a99468e1 1.3.6.1.2.1.7.5.1.2.169.148.104.225.14|2|14 1.3.6.1.2.1.7.7.1.1.4.0.127.2.0.137.182|2|4 1.3.6.1.2.1.7.7.1.2.4.0.127.2.0.137.182|4| 1.3.6.1.2.1.7.7.1.3.4.0.127.2.0.137.182|66|127 1.3.6.1.2.1.7.7.1.4.4.0.127.2.0.137.182|2|2 1.3.6.1.2.1.7.7.1.5.4.0.127.2.0.137.182|4| 1.3.6.1.2.1.7.7.1.6.4.0.127.2.0.137.182|66|137 1.3.6.1.2.1.7.7.1.7.4.0.127.2.0.137.182|66|182 1.3.6.1.2.1.7.7.1.8.4.0.127.2.0.137.182|66|185 1.3.6.1.2.1.7.8.0|70|9808059939656837207 1.3.6.1.2.1.7.9.0|70|10931009272993024622 # End of UDP-MIB, 16 OID(s) dumped If you wish to specify each value rather then rely on automatic random selection, use *--manual-value* command line switch. If you would rather have *snmpsim-record-mibs* tool to work out all the values by itself, consider raising the *--automatic-values* max probes value (default is 5000 probes). .. _snmpsim-record-traffic: Snooping SNMP traffic --------------------- SNMP traffic traveling in a network can also be a source of simulation data. The *snmpsim-record-traffic* tool can snoop live or process captured traffic finding SNMP Response messages there and using OID-value pairs for building *.snmprec* files. Since many SNMP agents can generate traffic over network within the a snooping sessions, the *snmpsim-record-traffic* tool is designed to classify captured SNMP traffic on the per-Agent basis and build dedicated data file for each Agent seen on the network. The *--output-dir=* command-line option specifies a directory where *snmpsim-record-traffic* tool would put generated data files into. Data files paths are crafted so that Simulator would act closer to the prototype Agents meaning: 1. Data files for each Agent is put under a separate directory resembling Simulator's transport IDs which correspond to UDP ports Simulator is listening on. 2. Original SNMPv1/v2c community names are preserved. Imagine we have two SNMP Agents (192.168.1.1 & 192.168.1.2) sending responses over a network we are snooping on. Here's a tcpdump report just to illustrate the idea: .. code-block:: bash # tcpdump -i lo listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes 20:05:20.799706 IP 192.168.1.9.55803 > 192.168.1.1.snmp: GetRequest(28) system .sysDescr.0 20:05:20.800027 IP 192.168.1.1.snmp > 192.168.1.9.55803: GetResponse(92) system.sysDescr.0="Linux jupiter 2.6.37.6-smp #2 SMP Fri May 17 22:03:50 CDT 2013 i686" 20:05:21.125421 IP 192.168.1.9.55803 > 192.168.1.2.snmp: GetRequest(28) system.sysDescr.0 20:05:21.924022 IP 192.168.1.2.snmp > 192.168.1.9.55803: GetResponse(92) system.sysDescr.0="Linux saturn 2.6.37.4-smp #2 SMP Fri May 10 21:31:32 CDT 2013 i686" The *pcap2dev* tool would create two directories with fixed prefix (1.3.6.1.6.1.1) and increasing suffix parts (0 & 1) to put generated data files for each Agent there. That is, all data files for Agent 192.168.1.1 would go under 1.3.6.1.6.1.1.0/ while data files for Agent 192.168.1.2 would end up in 1.3.6.1.6.1.1.1/. Snooped SNMP communities also take part in data file path creation -- they appear as a last component of the path. For example, if Agent 192.168.1.1 used SNMP communities 'wallace' and 'gromit' (on different occasions) and Agent 192.168.1.2 responded with community 'cheese', generated data files would look like this: .. code-block:: bash $ tree /tmp/recording /tmp/recording |--- 1.3.6.1.6.1.1.0 | | | ---- gromit.snmprec | | | ---- wallace.snmprec | |--- 1.3.6.1.6.1.1.1 | ---- cheese.snmprec To build data files from a network capture file, use *--capture-file=* command-line option. Capture file format should be either `pcap or pcap-ng `_. Most capturing tools (like `tcpdump `_) support these file formats. You could also use `tcpdump filter `_ as a parameter to *--packet-filter=* option to narrow packets selection criteria. Default packet filter is *udp and src port 161*. Examples ++++++++ With all that theory in mind, we can now run a live snooping session: .. code-block:: bash # snmpsim-record-traffic --output-dir=/tmp/recording --listen-interface=lo Listening on interface lo in non-promiscuous mode Applying packet filter "udp and src port 161" Listening on interface "lo", kill me when you are done. ^C Shutting down process... Creating simulation context 1.3.6.1.6.1.1.0/gromit at /tmp/recording/1.3.6.1.6.1.1.0/gromit.snmprec Creating simulation context 1.3.6.1.6.1.1.0/wallace at /tmp/recording/1.3.6.1.6.1.1.0/wallace.snmprec Creating simulation context 1.3.6.1.6.1.1.1/cheese at /tmp/recording/1.3.6.1.6.1.1.1/cheese.snmprec PCap statistics: packets snooped: 64 packets dropped: 24 packets dropped: by interface 0 SNMP statistics: empty packets: 0 OIDs seen: 19 UDP packets: 19 Response PDUs seen: 19 contexts seen: 3 SNMP exceptions: 0 SNMP errors: 0 snapshots taken: 0 agents seen: 2 unknown L2 protocol: 0 IP packets: 19 bad packets: 0 Here's one of data files produced: .. code-block:: bash $ cat /tmp/recording/1.3.6.1.6.1.1.0/gromit.snmprec 1.3.6.1.2.1.1.1.0|4|Linux jupiter 2.6.37.6-smp #2 SMP Fri May 17 22:03:50 CDT 2013 i686 1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10 1.3.6.1.2.1.1.3.0|67|311441639 1.3.6.1.2.1.1.4.0|4|postmaster@jupiter 1.3.6.1.2.1.1.5.0|4|jupiter 1.3.6.1.2.1.1.6.0|4|Jupiter 1.3.6.1.2.1.1.8.0|67|1 You can now move data files into your Simulator's data directory and fire up simulation. The *snmpsim-record-traffic* tool can also invoke :ref:`variation modules ` to feed recorded data through them. .. _snmpwalk-dump: Using snmpwalk reporting ------------------------ In some cases you may not be able to run :ref:`snmpsim-record-commands ` against a donor device. That can happen, for instance, if you can't setup *snmpsim-record-commands* on a system from where donor device is available or donor device is gone leaving you with just Net-SNMP's *snmpwalk* dumps someone have collected for you. Simulator provides limited support for snmpwalk-generated data files. Just save *snmpwalk* output into a file with *.snmpwalk* suffix and put it under the *--data-dir*. Once Simulator finds and indexes the *.snmpwalk* files, it will report them just as it does for its native *.snmprec* files. .. code-block:: bash $ snmpwalk -v2c -c public -ObentU localhost 1.3.6 > myagent.snmpwalk .. note:: Make sure you get *snmpwalk* producing plain OIDs and values! By default snmpwalk tries to beautify raw data from Agent with MIB information. As beautified data may not contain OIDs and numeric values, it could not be interpreted by the Simulator. Therefore always run *snmpwalk* with the "-ObentU" options. The *.snmpwalk* lines that can't be parsed by the Simulator will be skipped and details reported to stdout for your further consideration. In particular, current implementation does not cope well with multi-line strings sometimes produced by the *snmpwalk* tool. Alternatively, you can convert the *.snmpwalk* files into *.snmprec* ones by running them through the :ref:`snmpsim-manage-records ` tool. .. _agent-pro-dump: Using Simple Agent Pro samples ------------------------------ Another possible format for taking and storing SNMP snapshots is SimpleSoft `Simple Agent Pro `_ data files. Although we have neither seen any documentation on its data files format nor ever owned or used Simple Agent Pro software, a sample data file `published on the Internet `_ reveals that SimpleAgentPro's file format is very similar to Net-SNMP's snmpwalk. It essentially looks like *snmpwalk* output with different field separators. .. note:: SNMP Simulator might not support certain features/dialects of SimpleAgentPro data files format so your mileage may vary. In case you store your SNMP snapshots archives in SimpleAgentPro's data files and wish to use them with this Simulator, just put your SimpleAgentPro-formatted SNMP snapshot information (excluding comments) into text files having *.sapwalk* suffix and let Simulator find and index them. Once completed, Simulator will report access information for them just as it does for its native *.snmprec* files. Alternatively, you can convert the *.sapwalk* files into *.snmprec* ones by running them through the :ref:`snmpsim-manage-records ` tool. snmpsim-1.2.2/docs/source/documentation/command-line-options.rst0000644000000000000000000003547013615410400022022 0ustar00 Command-line options ==================== .. toctree:: :maxdepth: 2 The SNMP Simulator suite consists of a handful of command-line tools that take command-line options. Common options -------------- **--debug** ++++++++++++++++ The *--debug* option makes the daemon emitting detailed log of SNMP protocol related debugging. Debugging can be enabled for all or for just some of the SNMP engine subsystems by adding their names to the *--debug* option. Recognized SNMP debugging options include: * *io* -- report raw network traffic * *msgproc* -- report SNMP message processing * *secmod* -- report SNMP security module operations * *mibbuild* -- report on MIB loading and processing * *mibinstrum* -- report agent MIB operations * *acl* -- report MIB access access control operations * *proxy* -- report on SNMP version translation operations * *app* -- application-specific debugging * *all* -- enable full SNMP debugging SNMP debugging is fully disabled by default. **--debug-asn1** ++++++++++++++++ SNMP is backed by the `ASN.1 `_ for data representation and serialization purposes. The *--debug-asn1* option makes the tools emitting detailed log of ASN.1 data de/serialization. Debugging can be enabled for either encoder or decoder, or for everything ASN.1 related by adding their names to the *--debug-asn1* option. Recognized ASN.1 debugging options include: * *encoder* -- debug data serialization * *decoder* -- debug data deserialization * *all* -- enable full ASN.1 debugging ASN.1 debugging is fully disabled by default. **--logging-method** ++++++++++++++++++++ Some of the SNMP Simulator tools can log using one of the following methods. The default is *stderr*. **--logging-method=syslog** ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The *syslog* logging method requires the following sub-options: .. code-block:: bash --logging-method=syslog:facility[:address[:port:[tcp|udp]]]] Where: * *facility* -- one of the recognized syslog service `facilities `_ * *address* -- can be either an absolute path to a local socket or network address where syslog service is listening (optional) * *port* -- if network address of the syslog service is used for *address*, *port* be a TCP or UDP port number (optional) * *tcp* or *udp* -- TCP (stream) or UDP (datagram) protocol to use for syslog service communication (optional) **--logging-method=file** ~~~~~~~~~~~~~~~~~~~~~~~~~ The *file* logging method redirects daemon logging into a local file. The log file could be made automatically rotated based on time or size criteria. The following sub-options are supported: .. code-block:: bash --logging-method=file:path[:criterion] Where: * *path* -- path to a log file * *criterion* -- should consist of a number followed by one of the specifiers: - *k* -- rotate when file size exceeds N kilobytes - *m* -- rotate when file size exceeds N megabytes - *g* -- rotate when file size exceeds N gigabytes - *S* -- rotate when file age exceeds N seconds - *M* -- rotate when file age exceeds N minutes - *H* -- rotate when file age exceeds N hours - *D* -- rotate when file age exceeds N days **--logging-method=stdout/stderr** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When *stdout* or *stderr* logging methods are used, daemon log messages are directed to either process standard output or standard error stream. **--logging-method=null** ~~~~~~~~~~~~~~~~~~~~~~~~~ The *null* logging method completely inhibits all daemon logging. **--log-level** +++++++++++++++ The *--log-level* option limits the minimum severity of the log messages to actually log. Recognized log levels are: * *debug* -- log at all levels * *info* - log informational and error messages only * *error* - log error messages only SNMP command responders ----------------------- SNMP Simulator can be run as *snmpsim-command-responder* (full version) or *snmpsim-command-responder-lite* (lightweight version). Both programs take the following common command-line options. Common command responders options --------------------------------- The lightweight SNMP command responder implementation is limited to SNMP v1 and v2c protocol versions. Full SNMP command responder implementation supports all SNMP versions. The following options are respected by both SNMP command responder applications. **--daemonize** +++++++++++++++ Unless *--daemonize* option is given, the daemon will remain an interactive process. With the *--daemonize* option, the daemon will detach itself from user terminal, close down standard I/O streams etc. **--process-user** & **--process-group** ++++++++++++++++++++++++++++++++++++++++ It is generally safer to run daemons under a non-privileged user. However, it may be necessary to, at least, start SNMP Simulator parts as root to let the process bind to privileged ports (161/udp for SNMP by default). In this case it may make sense to drop process privileges upon initialization by becoming *--process-user* belonging to *--process-group*. **--pid-file** ++++++++++++++ Especially when running in *--daemonize* mode, it might be handy to keep track of UNIX process ID allocated to the running daemon. Primarily, this can be used for killing or restarting the process. The *--pid-file* option can be used to specify a disk file where daemon would store its PID. Default is not to create PID file. **--cache-dir** +++++++++++++++ Specifies path to directory for temporary indices used for fast simulation data lookup. The indices for all .snmprec files will be built on process start unless they already exist and not outdated. Default is `$TEMPDIR/snmpsim`. **--reporting-method** ++++++++++++++++++++++ SNMP command responder applications can collect and periodically emit various activity metrics. The default is *null* that disables activity collection and reporting. **--reporting-method=fulljson** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The *fulljson* activity reporting method collects and reports highly detailed activity metrics. It may become expensive when simulating thousands of transport endpoints or SNMP agents. The *fulljson* reporting method supports the following sub-options: .. code-block:: bash --reporting-method=fulljson:reports-dir Where: * *reports-dir* -- location on the filesystem where this reporting module should dump collected metrics. **--reporting-method=minimaljson** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The *minimaljson* activity reporting method collects and reports minimal activity metrics. Essentially, it collapses individual metrics for transport endpoints, agents, data files into one. The *minimaljson* reporting method supports the following sub-options: .. code-block:: bash --reporting-method=minimaljson:reports-dir Where: * *reports-dir* -- location on the filesystem where this reporting module should dump collected metrics. **--variation-modules-dir** +++++++++++++++++++++++++++ Specifies path to the directory where SNMP simulator should look for variation modules. All modules found there will be imported and initialized for further use from the .snmprec files. Default search path is dependent on the platform. On Linux it is: * `$HOME/.snmpsim/variation` * `/usr/snmpsim/variation` * `/usr/share/snmpsim/variation` * `/variation` **--variation-module-options** ++++++++++++++++++++++++++++++ Some variation modules accept configuration options. These options could be given in the form of `:`-separated positional arguments: .. code-block:: bash --variation-module-options=/data` **--agent-udpv4-endpoint** ++++++++++++++++++++++++++ Bind SNMP agent to the given UDP-over-IPv4 transport endpoint in the form of *IP:port*. Each occurrence of this option creates a new transport endpoint. All SNMP engines created afterwards (by *--v3-engine-id* option) up to the next *--agent-* option will reside behind this transport endpoint. .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:161 .. note:: Binding ports less than 1024 on UNIX requires superuser privileges. **--agent-udpv6-endpoint** ++++++++++++++++++++++++++ Bind SNMP agent to the given UDP-over-IPv6 transport endpoint in the form of *[IP]:port*. Each occurrence of this option creates a new transport endpoint. All SNMP engines created afterwards (by *--v3-engine-id* option) up to the next *--agent-* option will reside behind this transport endpoint. .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=[::1]:161 .. note:: Binding ports less than 1024 on UNIX requires superuser privileges. Full version command responder options -------------------------------------- Full version of SNMP command responder is based on SNMPv3 architecture, it is capable of handling all SNMP versions i.e. 1, 2c qnd 3. Full version of SNMP command responder understand all options of the lite version, plus the following SNMPv3-specific options. **--args-from-file** ++++++++++++++++++++ All command-line options to *snmpsim-command-responder* could be stored in a file and passes through this option. File could be easier to manage, and does not impose any limit on the length of the command line. **--v3-only** +++++++++++++ SNMP simulator serves simulation data over both SNMP v1/v2c and SNMPv3 protocols. With the *--v3-only* flag in effect, SNMPv1/v2c agents will not be configured what saves a bit of memory and startup time. Default is to configure SNMPv1/v2c and SNMPv3. **--v3-engine-id** ++++++++++++++++++ SNMP engine identifier that creates a new, independent instance of SNMP engine. All the following *--v3-* options up to another *--v3-engine-id* option apply to the SNMP engine being configured. .. code-block:: bash snmpsim-command-responder --v3-engine-id=0102030405070809 ... .. note:: The *-v3-engine-id* option expects a hex string or `auto` for automatic value. The default is an autogenerated value. **--v3-context-engine-id** ++++++++++++++++++++++++++ SNMP entity can have access to many instances of the same collection of MIB objects. Each such collection is called *context*. A context is identified by the Context Engine ID and a Context Name that identifies the specific context. In other words, to identify an individual item of SNMP management information, four elements are required: 1. a ContextEngineID 2. a ContextName 3. an object type, and 4. its instance identification The default for *--v3-context-engine-id* option is the same value as *--v3-engine-id*. **--v3-user** +++++++++++++ SNMP USM user name to use for SNMPv3 authentication and authorization purposes. **--v3-auth-key** +++++++++++++++++ SNMP USM message authentication key. .. note:: Must be 8 or more characters. **--v3-auth-proto** +++++++++++++++++++ SNMPv3 message authentication protocol to use. Valid values are: +--------+----------------+-------------+ | *ID* | *Algorithm* | *Reference* | +--------+----------------+-------------+ | NONE | - | RFC3414 | +--------+----------------+-------------+ | MD5 | HMAC MD5 | RFC3414 | +--------+----------------+-------------+ | SHA | HMAC SHA-1 128 | RFC3414 | +--------+----------------+-------------+ | SHA224 | HMAC SHA-2 224 | RFC7860 | +--------+----------------+-------------+ | SHA256 | HMAC SHA-2 256 | RFC7860 | +--------+----------------+-------------+ | SHA384 | HMAC SHA-2 384 | RFC7860 | +--------+----------------+-------------+ | SHA512 | HMAC SHA-2 512 | RFC7860 | +--------+----------------+-------------+ **--v3-priv-key** +++++++++++++++++ SNMP USM message encryption key. .. note:: Must be 8 or more characters. **--v3-priv-proto** +++++++++++++++++++ SNMPv3 message encryption protocol to use. Valid values are: +------------+------------------------+----------------------+ | *ID* | *Algorithm* | *Reference* | +------------+------------------------+----------------------+ | NONE | - | RFC3414 | +------------+------------------------+----------------------+ | DES | DES | RFC3414 | +------------+------------------------+----------------------+ | AES | AES CFB 128 | RFC3826 | +------------+------------------------+----------------------+ | AES192 | AES CFB 192 | RFC Draft | +------------+------------------------+----------------------+ | AES256 | AES CFB 256 | RFC Draft | +------------+------------------------+----------------------+ | AES192BLMT | AES CFB 192 Blumenthal | RFC Draft | +------------+------------------------+----------------------+ | AES256BLMT | AES CFB 256 Blumenthal | RFC Draft | +------------+------------------------+----------------------+ | 3DES | Triple DES EDE | RFC Draft | +------------+------------------------+----------------------+ snmpsim-1.2.2/docs/source/documentation/index.rst0000644000000000000000000000124313615410400017064 0ustar00 Documentation ------------- SNMP Simulator suite contains a handful of command-line tools implementing different stages of simulation work flow. In general, SNMP simulator user might want to prepare some SNMP simulation data in one way or the other, then run SNMP agent simulator tool over simulation data to represent SNMP agents on the network. Then user software might query those simulated agents to test its own behavior. .. toctree:: :maxdepth: 2 command-line-options simulating-agents managing-simulation-data addressing-agents simulation-with-variation-modules building-simulation-data recording-with-variation-modules tips-and-tricks snmpsim-1.2.2/docs/source/documentation/managing-simulation-data.rst0000644000000000000000000001227613615410400022637 0ustar00 .. _managing-simulation-data: Simulation data =============== SNMP agent simulation revolves around the contents of *.snmprec* files. .. _snmprec: File format ----------- The *.snmprec* file format is optimised to be compact, human-readable and inexpensive to parse. It's also important to store full and exact response information in a most intact form. Here's an example data file content: .. code-block:: bash 1.3.6.1.2.1.1.1.0|4|Linux 2.6.25.5-smp SMP Tue Jun 19 14:58:11 CDT 2007 i686 1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10 1.3.6.1.2.1.1.3.0|67|233425120 1.3.6.1.2.1.2.2.1.6.2|4x|00127962f940 1.3.6.1.2.1.4.22.1.3.2.192.21.54.7|64x|c3dafe61 There is a pipe-separated triplet of *OID|tag|value* items where: * OID is a dot-separated set of numbers * Tag is a BER-encoded ASN.1 tag. A modifier can be appended to the tag number. The following modifiers are known: - *x* when the value is hexified (e.g. '0102') - *e* when the value is a Python string literal (e.g. '\x01\x02hello') - Colon-separated reference to a variation module * The value is either a printable string or a hexified string or a raw Python string. Unless it's a number. Valid tag values and their corresponding ASN.1/SNMP types are: * 2 - Integer32 * 4 - OCTET STRING * 5 - NULL * 6 - OBJECT IDENTIFIER * 64 - IpAddress * 65 - Counter32 * 66 - Gauge32 * 67 - TimeTicks * 68 - Opaque * 70 - Counter64 Besides plain-text form, compressed *.snmprec.bz2* files are also supported. .. _snmpsim-manage-records: Managing data files ------------------- The *snmpsim-manage-records* tool is designed to perform a few handy operations on the data files. If you possess *.snmpwalk* or *.sapwalk* snapshots and wish to convert them into Simulator's native *.snmprec* data file format (what can be required for using variation modules), run the *snmpsim-manage-records* tool like this: .. code-block:: bash $ snmpsim-manage-records --input-file=linux.snmpwalk \ --source-record-type=snmpwalk 1.3.6.1.2.1.1.1.0|4|Linux cray 2.6.37.6-smp #2 SMP Sat Apr 9 23:39:07 CDT 2011 i686 1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10 1.3.6.1.2.1.1.3.0|67|121722922 1.3.6.1.2.1.1.4.0|4|Root (configure /etc/snmp/snmp.local.conf) 1.3.6.1.2.1.1.5.0|4|new system name 1.3.6.1.2.1.1.6.0|4|KK12 (edit /etc/snmp/snmpd.conf) 1.3.6.1.2.1.1.8.0|67|0 1.3.6.1.2.1.1.9.1.2.1|6|1.3.6.1.6.3.11.2.3.1.1 1.3.6.1.2.1.1.9.1.2.2|6|1.3.6.1.6.3.15.2.1.1 1.3.6.1.2.1.1.9.1.2.3|6|1.3.6.1.6.3.10.3.1.1 ... # Records: written 3711, filtered out 0, deduplicated 0, broken 0, variated 0 SNMP Simulator requires data files to be sorted (by OID) and containing no duplicate OIDs. In case your data file does not comply with these requirements for some reason, you could pass it through the *snmpsim-manage-records* tool to fix data file: .. code-block:: bash $ snmpsim-manage-records --input-file=tcp-mib.snmprec --sort-records --ignore-broken-records --deduplicate-records 1.3.6.1.2.1.6.1.0|2|1 1.3.6.1.2.1.6.2.0|2|4 1.3.6.1.2.1.6.3.0|2|2 1.3.6.1.2.1.6.4.0|2|4 ... 1.3.6.1.2.1.6.20.1.2.2.0.432|4| 1.3.6.1.2.1.6.20.1.3.2.0.432|66|80 1.3.6.1.2.1.6.20.1.4.2.0.432|66|1524968792 # Records: written 33, filtered out 0, deduplicated 0, broken 0, variated 0 If you have a huge data file and wish to use just a part of it for simulation purposes, snmpsim-manage-records tool could cut a slice form a data file and store records in a new one: .. code-block:: bash $ snmpsim-manage-records --input-file=tcp-mib.snmprec \ --start-oid=1.3.6.1.2.1.6.13 --stop-oid=1.3.6.1.2.1.6.14 1.3.6.1.2.1.6.13.1.1.72.192.51.208.2.234.233.215.7.3|2|1 1.3.6.1.2.1.6.13.1.2.72.192.51.208.2.234.233.215.7.3|64x|8b896863 1.3.6.1.2.1.6.13.1.3.72.192.51.208.2.234.233.215.7.3|2|3 1.3.6.1.2.1.6.13.1.4.72.192.51.208.2.234.233.215.7.3|64x|4f1182fe 1.3.6.1.2.1.6.13.1.5.72.192.51.208.2.234.233.215.7.3|2|3 # Records: written 5, filtered out 28, deduplicated 0, broken 0, variated 0 Merge of multiple data files into a single data file is also supported: .. code-block:: bash $ snmpsim-manage-records --input-file=tcp-mib.snmprec \ --input-file=udp-mib.snmprec --sort-records \ --deduplicate-records 1.3.6.1.2.1.6.1.0|2|1 1.3.6.1.2.1.6.2.0|2|4 1.3.6.1.2.1.6.3.0|2|2 1.3.6.1.2.1.6.4.0|2|4 ... 1.3.6.1.2.1.7.8.0|70|3896031866066683889 1.3.6.1.2.1.7.9.0|70|3518073560493506800 # Records: written 49, filtered out 0, deduplicated 0, broken 0, variated 0 Having string values more human-readable may be more convenient in the course of adjusting simulation data, debugging etc. By default, strings in simulation data are hexified. By passing such *.snmprec* file through the *snmpsim-manage-records --escaped-strings* call, you can convert your *.snmprec* data into Python string literal representation: .. code-block:: bash $ head data/sample.snmprec 1.3.6.1.2.1.55.1.5.1.8.2|4x|00127962f940 $ $ snmpsim-manage-records --source-record-type=snmprec \ --input-file=data/sample.snmprec --escaped-strings 1.3.6.1.2.1.55.1.5.1.8.2|4e|\x00\x12yb\xf9@ # Records: written 1, filtered out 0, deduplicated 0, broken 0, variated 0 snmpsim-1.2.2/docs/source/documentation/recording-with-variation-modules.rst0000644000000000000000000003637413615410400024357 0ustar00 .. _recording-with-variation-modules: Recording with variation modules ================================ Some valuable simulation information may also be collected in the process of recording snapshots off the live SNMP Agent. Examples include changes in the set of OIDs in time, changes in numeric values, request processing times and so on. To facilitate capturing such information, some of the stock variation modules support snapshots recording mode. To invoke a variation module while recording SNMP Agent with the :ref:`snmpsim-record-commands ` tool, pass its name via the *--variation-module* command-line option. Additional variation module parameters could also be passed through the *--variation-module-options* switch. The following standard modules support the recording feature: * The :ref:`numeric ` module produces a non-decreasing sequence of integers over time * The :ref:`sql ` module reads/writes var-binds from/to a SQL database * The :ref:`redis ` module reads/writes var-binds from/to a no-SQL key-value store * The :ref:`delay ` module delays SNMP response by specified or random time * The :ref:`multiplex ` module uses a time series of *.snmprec* files picking one at a time. .. _record-numeric: Numeric module -------------- The numeric module can be used for capturing initial values of Managed Objects and calculating a coefficient to a linear function in attempt to approximate live values changes in time. In case value change law is not linear, custom approximation function should be used instead. The numeric module supports the following comma-separated key:value options whilst running in recording mode: * *taglist* - a dash-separated list of *.snmprec* tags indicating SNMP value types to apply numeric module to. Valid tags are: - 2 - Integer - 65 - Counter32 - 66 - Gauge32 - 67 - TimeTicks - 70 - Counter64 Default is empty list. * *iterations* - number of recording cycles to run over the same portion of SNMP agent MIB. There's no point in values beyond 2 for purposes of modelling approximation function. Default is 1. * *period* - Agent walk period in seconds. Default is 10 seconds. * *addon* - a single *.snmprec* record scope *key=value* parameter for the *numeric* module to be used whilst running in variation mode. Multiple add-on parameters can be used. Default is empty. Examples ++++++++ In the examples the :ref:`snmpsim-record-commands ` tool will be used. .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --start-oid=1.3.6.1.2.1.2 --stop-oid=1.3.6.1.2.1.3 \ --variation-module=numeric \ --variation-module-options=taglist:65,iterations:2,period:15 \ Scanning "/usr/local/share/snmpsim/variation" directory for variation modules...numeric module loaded SNMP version 2c Community name: public Querying UDP/IPv4 agent at demo.pysnmp.com:161 Initializing variation module: numeric...OK numeric: waiting 0.77 sec(s), 111 OIDs dumped, 1 iterations remaining... ... 1.3.6.1.2.1.2.2.1.6.4|4x|008d120f4fa4 1.3.6.1.2.1.2.2.1.7.1|2|1 1.3.6.1.2.1.2.2.1.9.2|67|0 1.3.6.1.2.1.2.2.1.10.1|65:numeric|rate=3374,initial=641734,increasing=1 1.3.6.1.2.1.2.2.1.10.2|65:numeric|rate=0,initial=0,increasing=1 1.3.6.1.2.1.2.2.1.10.4|65:numeric|rate=1159,initial=32954879,increasing=1 1.3.6.1.2.1.2.2.1.11.1|65:numeric|rate=86,initial=12238,increasing=1 1.3.6.1.2.1.2.2.1.21.1|66|0 ... Shutting down variation modules: numeric...OK OIDs dumped: 224, elapsed: 15.53 sec, rate: 20.00 OIDs/sec In the above example we have run two iterations against a subset of Managed Objects at an Agent requesting numeric module to configure itself into generated *.snmprec* data for Counter32-typed objects (ID 65). Produced *.snmprec* file could be used for simulation as-is or edited by hand to change variation module behaviour on on a per-OID basis. .. _record-delay: Delay module ------------ The delay module can be used for capturing request processing time when recording SNMP agent. Examples ++++++++ .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --start-oid=1.3.6.1.2.1.2 --stop-oid=1.3.6.1.2.1.3 \ --variation-module=delay Scanning "/usr/local/share/snmpsim/variation" directory for variation modules...delay module loaded SNMP version 2c Community name: public Querying UDP/IPv4 agent at demo.pysnmp.com:161 Initializing variation module: delay...OK 1.3.6.1.2.1.2.1.0|2:delay|value=5,wait=8 1.3.6.1.2.1.2.2.1.1.1|2:delay|value=1,wait=32 1.3.6.1.2.1.2.2.1.6.4|4x:delay|hexvalue=008d120f4fa4,wait=20 ... Shutting down variation modules: delay...OK OIDs dumped: 224, elapsed: 15.53 sec, rate: 20.00 OIDs/sec Produced *.snmprec* file could be used for Simulation as-is or edited by hand to change delay variation. .. _record-multiplex: Multiplex module ---------------- The multiplex module can record a series of snapshots at specified period of time. Recorded *.snmprec* snapshots could then be used for simulation by multiplex module. The multiplex module supports the following comma-separated *key:value* options whilst running in recording mode: * *dir* - directory for produced *.snmprec* files * *recordtype* - simulation data file type to produce (e.g. *snmprec*, *snmprec.bz2*). Default is *snmprec*. * *iterations* - number of recording cycles to run over the same portion of SNMP agent MIB. There's no point in values beyond 2 for purposes of modelling approximation function. Default is 1. * *period* - Agent walk period in seconds. Default is 10 seconds. * *addon* - a single *.snmprec* record scope *key=value* parameter for the *multiplex* module to be used whilst running in variation mode. Multiple add-on parameters can be used. Default is empty. Examples ++++++++ .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --start-oid=1.3.6.1.2.1.2 --stop-oid=1.3.6.1.2.1.3 \ --output-file=data/multiplex.snmprec \ --variation-module=multiplex \ --variation-module-options=dir:data/multiplex,iterations:5,period:15 Scanning "/usr/local/share/snmpsim/variation" directory for variation modules...multiplex module loaded SNMP version 2c Community name: public Querying UDP/IPv4 agent at demo.pysnmp.com:161 Initializing variation module: multiplex...OK multiplex: writing into data/multiplex/00000.snmprec file... multiplex: waiting 14.78 sec(s), 45 OIDs dumped, 5 iterations remaining... ... multiplex: writing into data/multiplex/00005.snmprec file... Shutting down variation modules: multiplex...OK OIDs dumped: 276, elapsed: 75.76 sec, rate: 3.64 OIDs/sec Besides individual *.snmprec* snapshots, the "main" *.snmprec* file will also be written: .. code-block:: bash $ cat data/multiplex.snmprec 1.3.6.1.2.1.2|:multiplex|period=15.00,dir=data/multiplex where the multiplex module is configured for specific OID subtree (actually, specified in *--start-oid*). Although multiplex-generated *.snmprec* files can also be addressed directly by Simulator, to benefit from the time series nature of the collected data, it's better to simulate based on the "main" *.snmprec* file and the multiplex variation module. .. _record-sql: SQL module ---------- The *sql* module can record a snapshot of SNMP agent's set of Managed Objects and store it in a SQL database. Recorded snapshots could then be used for simulation by the *sql* module running in variation mode. Module configuration parameters described on the :ref:`simulation ` page are also applicable to the recording. Examples ++++++++ Running with SQLite DB backend: .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com --start-oid=1.3.6.1.2.1.2 --stop-oid=1.3.6.1.2.1.3 --output-file=data/sql.snmprec --variation-module=sql --variation-module-options=dbtype:sqlite3,database:/tmp/snmpsim.db,dbtable:snmprec Scanning "/usr/local/share/snmpsim/variation" directory for variation modules... sql module loaded SNMP version 2c Community name: public Querying UDP/IPv4 agent at demo.pysnmp.com:161 Initializing variation module: sql...OK Shutting down variation modules: sql...OK OIDs dumped: 45, elapsed: 0.21 sec, rate: 213.00 OIDs/sec By this point you'd get the *data/sql.snmprec* file where *sql* module is configured for OID subtree (taken from *--start-oid* parameter): .. code-block:: bash $ cat data/sql.snmprec 1.3.6.1.2.1.2.2|:sql|snmprec and SQLite database */tmp/snmpsim.db* having SQL table "snmprec" with the following contents: .. code-block:: bash $ sqlite3 /tmp/snmpsim.db SQLite version 3.7.5 sqlite> .schema snmprec CREATE TABLE snmprec (oid text, tag text, value text, maxaccess text); sqlite> select * from snmprec limit 1; 1. 3. 6. 1. 2. 1. 2. 2. 1. 1. 1|2|1|read-write .. note:: The OID is formatted in a way that each sub-oid is left-padded with up to 8 spaces (must be 10 chars in total) to make the ordering work properly with standard SQL sorting. The following :ref:`snmpsim-record-commands ` call push snapshots into MySQL database using native MySQL's Connector/Python driver: .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --output-file=data/sql.snmprec \ --variation-module=sql \ --variation-module-options=dbtype:mysql.connector,host:127.0.0.1, \ port:3306,user:snmpsim,password:snmpsim,database:snmpsim The above code assumes that you have the `MySQL Connector/Python driver `_ installed on the recording machine and a MySQL server running at 127.0.0.1 with MySQL user/password snmpsim/snmpsim having sufficient permissions for creating new tables. Another variation of MySQL server installation setup on a UNIX system employs UNIX domain socket for client-server communication. In that case the following command-line for :ref:`snmpsim-record-commands ` might work: .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --output-file=data/sql.snmprec \ --variation-module=sql --variation-module-options=dbtype:mysql.connector,unix_socket: \ /var/run/mysql/mysql.sock,user:snmpsim,password:snmpsim,database:snmpsim Alternatively, the `MySQL for Python `_ package could be used for SNMP Simulator's MySQL connection: .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --output-file=data/sql.snmprec \ --variation-module=sql \ --variation-module-options=dbtype:MySQLdb,host:127.0.0.1,port:3306, \ user:snmpsim,passwd:snmpsim,db:snmpsim Similar call but with the `PostgreSQL `_ DB as a backend data store: .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --output-file=data/sql.snmprec \ --variation-module=sql \ --variation-module-options=dbtype:psycopg2,database:snmpsim,user:snmpsim, \ password:snmpsim,dbtable:snmprec With the example above, the assumption is that you have the `Psycopg `_ module installed, PostgreSQL server running locally (accessed through default UNIX domain socket), DB user/password are snmpsim/snmpsim and this user has sufficient permissions to create new database tables (snmprec table will be created). When *sql* variation module is invoked in :ref:`simulaiton ` context, it can read, create and modify individual rows in the SQL database we just created. You could also modify the contents of such SQL tables, create SQL triggers to react to certain changes elsewhere. .. _record-redis: Redis module ------------ The *redis* module can record one or more snapshots of SNMP agent's set of Managed Objects and store it in `Redis key-value store `_. Recorded snapshots could then be replayed by *redis* module running in :ref:`variation mode `. Redis database schema and module configuration parameters explained on the :ref:`variation ` page is also applicable to the recording mode. The *redis* module supports the following comma-separated *key:value* options whilst running in recording mode: * *host* - Redis hostname or IP address. * *port* - Redis TCP port the server is listening on. * *unix_socket* - UNIX domain socket Redis server is listening on. * *db* - Redis database number. * *password* - Redis database admission password. * *key-spaces-id* - key spaces ID to use for recording a single or a series of snapshots * *iterations* - number of recording cycles to run over the same portion of SNMP agent MIB. There's no point in values beyond 2 for purposes of modelling approximation function. Default is 1. * *period* - Agent walk period in seconds. Default is 10 seconds. * *evalsha* - Redis server side `Lua script `_ to use for storing oid-value pairs in Redis. If this option is not given, bare Redis SET commands will be used instead. Examples ++++++++ Make the *redis* module for recording five snapshots of a demo SNMP Agent: .. code-block:: bash $ snmpsim-record-commands --agent-udpv4-endpoint=demo.pysnmp.com \ --start-oid=1.3.6.1.2.1.2 --stop-oid=1.3.6.1.2.1.3 \ --output-file=data/redis.snmprec \ --variation-module=redis \ --variation-module-options=host:127.0.0.1,port:6379,db:0,key-spaces-id:1111, \ iterations:5,period:30 Scanning "variation" directory for variation modules... Variation module "redis" loaded SNMP version 2c, Community name: public Querying UDP/IPv4 agent at 195.218.195.228:161 Initializing variation module... redis: using key-spaces-id 1111 Variation module "redis" initialization OK Sending initial GETNEXT request.... redis: done with key-space 0000001116 redis: 4 iterations remaining 85 OIDs dumped, waiting 30.00 sec(s)... redis: done with key-space 0000001115 redis: 3 iterations remaining 171 OIDs dumped, waiting 30.00 sec(s)... redis: done with key-space 0000001114 redis: 2 iterations remaining 257 OIDs dumped, waiting 30.00 sec(s)... redis: done with key-space 0000001113 redis: 1 iterations remaining 343 OIDs dumped, waiting 30.00 sec(s)... redis: done with key-space 0000001112 redis: 0 iterations remaining Shutting down variation module redis... Variation module redis shutdown OK OIDs dumped: 603, elapsed: 329.22 sec, rate: 0.00 OIDs/sec By this point you'd get the *data/redis.snmprec* file where *redis* module is configured for OID subtree (taken from the *--start-oid* parameter): .. code-block:: bash $ cat data/redis.snmprec 1.3.6|:redis|period=30.00,key-spaces-id=1111 When *redis* variation module is invoked in the :ref:`variation context `, it can read, create and modify individual OID-value pairs in Redis database we've just created. snmpsim-1.2.2/docs/source/documentation/simulating-agents.rst0000644000000000000000000003610613615410400021416 0ustar00 .. _simulating-agents: Simulating SNMP Agents ====================== The ``snmpsim-command-responder`` program performs actual SNMP agent simulation based on the simulation data provided. .. _simulation-data-location: Simulation Data --------------- SNMP agents simulation data ends up in :ref:`.snmprec ` files. Once SNMP request comes in, SNMP Simulator :ref:`constructs .snmprec file path ` and tries to locate it by searching through the following directories: * ``~/.snmpsim/data`` * ``/usr/local/share/snmpsim/data`` * ``{python-package-root}/data`` On Windows search paths are: * ``\Document and Settings\{user}\Application Data\SNMP Simulator\Data`` * ``\Program Files\SNMP Simulator\Data`` * ``{python-package-root}/data`` These directories are searched in the specified order till the first match. For example, a set up collection of ``.snmprec`` files would look like: .. code-block:: bash $ cd /usr/local/share $ find snmpsim/data snmpsim/data snmpsim/data/public.snmprec snmpsim/data/mib2dev snmpsim/data/mib2dev/ip-mib.snmprec snmpsim/data/mib2dev/host-resources-mib.snmprec snmpsim/data/mib2dev/tcp-mib.snmprec snmpsim/data/foreignformats snmpsim/data/foreignformats/linux.snmpwalk snmpsim/data/foreignformats/winxp.sapwalk snmpsim/data/variation snmpsim/data/variation/subprocess.snmprec snmpsim/data/variation/virtualtable.snmprec snmpsim/data/recorded snmpsim/data/recorded/linksys-system.snmprec snmpsim/data/recorded/udp-endpoint-table-walk.snmprec ... .. note:: The ``--data-dir`` option can be used to prepend custom directories to the search path. However, if SNMP engine is configured explicitly (via the ``--v3-engine-id`` option), then custom data directories will override the default search path. .. _snmpsim-command-responder: SNMP Simulator Daemon --------------------- The ``snmpsim-command-responder`` tool hosts multiple independent SNMP Command Responders. It can run multiple SNMP engines exchanging data over multiple network interfaces. Each SNMP engine instance can serve many independent sets of SNMP management objects sourced from :ref:`local .snmprec files ` or :ref:`variation modules `. .. _multiple-listen-interfaces: Multiple Network Interfaces +++++++++++++++++++++++++++ SNMP Simulator daemon can listen at multiple local IP interfaces and/or UDP ports. Just pass multiple ``--agent-udpv4-endpoint``/``--agent-udpv6-endpoint`` command line parameters carrying addresses to listen on. Whenever you wish Simulator to listen on thousands of local interfaces and/or ports, use the ``--agent-udpv4-endpoints-list``/``--agent-udpv6-endpoints-list`` options. These options expect to refer to a plain text file containing newline-separated list of transport endpoints for Simulator to listen on. .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:1611 \ --agent-udpv6-endpoint='[::1]:1161' Scanning "/home/user/.snmpsim/variation" directory for variation modules... no directory Scanning "/usr/local/share/snmpsim/variation" directory for variation modules... 8 more modules found Initializing variation modules: notification... OK sql... FAILED: database type not specified numeric... OK subprocess... OK delay... OK multiplex... OK error... OK writecache... OK Scanning "/home/user/.snmpsim/data" directory for *.snmpwalk, *.MVC, *.sapwalk, *.snmprec, *.dump data files... no directory Scanning "/usr/local/share/snmpsim/data" directory for *.snmpwalk, *.MVC, *.sapwalk, *.snmprec, *.dump data files... ================================================================== Index /tmp/snmpsim/usr_local_share_snmpsim_data_public.dbm does not exist for data file data/public.snmprec Building index /tmp/snmpsim/usr_local_share_snmpsim_data_public.dbm for data file /usr/local/share/snmpsim/data/public.snmprec (open flags "n")...... 133 entries indexed Data file /usr/local/share/snmpsim/data/public.snmprec, dbhash-indexed, closed SNMPv1/2c community name: public SNMPv3 context name: 4c9184f37cff01bcdc32dc486ec36961 -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Index /tmp/snmpsim/usr_local_share_snmpsim_data_recorded_linksys-system.dbm does not exist for data file /usr/local/share/snmpsim/data/recorded/ linksys-system.snmprec Building index /tmp/snmpsim/usr_local_share_snmpsim_data_recorded_linksys- system.dbm for data file /usr/local/share/snmpsim/data/recorded/linksys- system.snmprec (open flags "n")......6 entries indexed Data file /usr/local/share/snmpsim/data/recorded/linksys-system.snmprec, dbhash-indexed, closed SNMPv1/2c community name: recorded/linksys-system SNMPv3 context name: 1a764f7fd0e7b0bf98bada8fe723e488 -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ... ... ... SNMPv3 credentials: Username: simulator Authentication key: auctoritas Authentication protocol: MD5 Encryption (privacy) key: privatus Encryption protocol: DES Listening at: UDP/IPv4 endpoint 127.0.0.1:1611, transport ID 1.3.6.1.6.1.1.0 UDP/IPv6 endpoint ::1:1161, transport ID 1.3.6.1.2.1.100.1.2.0 .. note:: An unprivileged port 1611 has been chosen in this example to avoid running ``snmpsim-command-responder`` process as root. By this point you can run you favorite SNMP Manager to talk to either of the two simulated devices through whatever transport you prefer. For instance, to talk to simulated Linux box over SNMP v2c through UDP over IPv4 run: .. code-block:: bash $ snmpwalk -On -v2c -c recorded/linksys-system localhost:1161 1.3.6 .1.3.6.1.2.1.1.1.0 = STRING: BEFSX41 .1.3.6.1.2.1.1.2.0 = OID: .1.3.6.1.4.1.3955.1.1 .1.3.6.1.2.1.1.3.0 = Timeticks: (638239) 1:46:22.39 .1.3.6.1.2.1.1.4.0 = STRING: Linksys .1.3.6.1.2.1.1.5.0 = STRING: isp-gw .1.3.6.1.2.1.1.6.0 = STRING: 4, Petersburger strasse, Berlin, Germany .1.3.6.1.2.1.1.8.0 = Timeticks: (4) 0:00:00.04 .1.3.6.1.2.1.1.8.0 = No more variables left in this MIB View ... To walk simulated 3com switch over SNMPv3 we'd run: .. code-block:: bash $ snmpwalk -On -v3 -n recorded/linksys-system \ -l authPriv -u simulator -A auctoritas -X privatus \ localhost:1161 1.3.6 .1.3.6.1.2.1.1.1.0 = STRING: BEFSX41 .1.3.6.1.2.1.1.2.0 = OID: .1.3.6.1.4.1.3955.1.1 .1.3.6.1.2.1.1.3.0 = Timeticks: (638239) 1:46:22.39 .1.3.6.1.2.1.1.4.0 = STRING: Linksys .1.3.6.1.2.1.1.5.0 = STRING: isp-gw .1.3.6.1.2.1.1.6.0 = STRING: 4, Petersburger strasse, Berlin, Germany .1.3.6.1.2.1.1.8.0 = Timeticks: (4) 0:00:00.04 .1.3.6.1.2.1.1.8.0 = No more variables left in this MIB View ... .. note:: The ``-n `` parameter passed to the ``snmpwalk`` tool addresses specific simulated device at SNMP Simulator daemon. .. _multiple-usm-users: Multiple USM Users ++++++++++++++++++ It is also possible to configure many SNMPv3 (USM) users to Simulator. Each set of ``--v3-user``, ``--v3-auth-key``, ``--v3-priv-key`` parameters adds one SNMPv3 user to Simulator. There is no correlation between SNMPv3 users and simulated resources, all users have the same view of the Simulator and the same access permissions. But you can use SNMPv3 contextNames and/or transport endpoints for addressing different data files e.g. simulated SNMP agents. .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1 \ --v3-user=wallace --v3-auth-key=testkey123 --v3-priv-key=testkey839 \ --v3-user=gromit --v3-auth-key=testkey564 --v3-priv-key=testkey6534 Scanning "/home/user/.snmpsim/variation" directory for variation modules... ... SNMPv3 EngineID 0x80004fb8056372617927fb76cc ------------------------------------------------------------------ SNMPv3 USM SecurityName: wallace SNMPv3 USM authentication key: testkey123, authentication protocol: MD5 SNMPv3 USM encryption (privacy) key: testkey839, encryption protocol: DES ------------------------------------------------------------------ SNMPv3 USM SecurityName: gromit SNMPv3 USM authentication key: testkey564, authentication protocol: MD5 SNMPv3 USM encryption (privacy) key: testkey6534, encryption protocol: DES Listening at UDP/IPv4 endpoint 127.0.0.1:161, transport ID 1.3.6.1.6.1.1.0 ... SNMP simulator supports many SNMPv3 authentication and encryption algorithms. For each user you can configure any authentication and any encryption (privacy) algorithm. .. _auth-algos: The following authentication algorithms are currently supported (via ``--v3-auth-proto=`` option): +--------+----------------+-------------+ | *ID* | *Algorithm* | *Reference* | +--------+----------------+-------------+ | NONE | - | RFC3414 | +--------+----------------+-------------+ | MD5 | HMAC MD5 | RFC3414 | +--------+----------------+-------------+ | SHA | HMAC SHA-1 128 | RFC3414 | +--------+----------------+-------------+ | SHA224 | HMAC SHA-2 224 | RFC7860 | +--------+----------------+-------------+ | SHA256 | HMAC SHA-2 256 | RFC7860 | +--------+----------------+-------------+ | SHA384 | HMAC SHA-2 384 | RFC7860 | +--------+----------------+-------------+ | SHA512 | HMAC SHA-2 512 | RFC7860 | +--------+----------------+-------------+ .. _priv-algos: The following privacy (encryption) algorithms are currently supported (via ``--v3-priv-proto=`` option): +------------+------------------------+----------------------+ | *ID* | *Algorithm* | *Reference* | +------------+------------------------+----------------------+ | NONE | - | RFC3414 | +------------+------------------------+----------------------+ | DES | DES | RFC3414 | +------------+------------------------+----------------------+ | AES | AES CFB 128 | RFC3826 | +------------+------------------------+----------------------+ | AES192 | AES CFB 192 | RFC Draft | +------------+------------------------+----------------------+ | AES256 | AES CFB 256 | RFC Draft | +------------+------------------------+----------------------+ | AES192BLMT | AES CFB 192 Blumenthal | RFC Draft | +------------+------------------------+----------------------+ | AES256BLMT | AES CFB 256 Blumenthal | RFC Draft | +------------+------------------------+----------------------+ | 3DES | Triple DES EDE | RFC Draft | +------------+------------------------+----------------------+ .. note:: The AES192, AES256 and 3DES are implemented based on `Blumenthal `_ and `Reeder `_ draft RFCs. Another configurable parameter is SNMPv3 snmpEngineId value. It's normally automatically generated but can also be configured through command line. .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1 \ --v3-engine-id=010203040505060809 Scanning "/home/user/.snmpsim/variation" directory for variation modules... ... SNMPv3 EngineID 0x010203040505060809 ------------------------------------------------------------------ SNMPv3 USM SecurityName: simulator SNMPv3 USM authentication key: auctoritas, authentication protocol: MD5 SNMPv3 USM encryption (privacy) key: privatus, encryption protocol: DES Listening at UDP/IPv4 endpoint 127.0.0.1:161, transport ID 1.3.6.1.6.1.1.0 .. note:: The ``SnmpEngineId`` value has to follow `certain format `_. .. _multiple-snmp-engine-ids: Multiple SNMP Engines +++++++++++++++++++++ SNMP Simulator could run many independent SNMP engines all within a single daemon process. SNMP managers could address particular SNMP Engine instance by querying it at a transport endpoint to which SNMP Engine is bound. Each SNMP Engine will have its own set of USM users and could serve its own ``--data-dir`` (or they can share a single directory). The logic of configuring specific parameters to different SNMP engines is to "scope" SNMP Engine parameters (like users, transports, data directory) within its ``--v3-engine-id`` fragment of Simulator command-line sequence of options. For example: .. code-block:: bash $ snmpsim-command-responder \ --v3-engine-id=010203040505060809 \ --v3-user=wallace --v3-auth-key=testkey123 \ --agent-udpv4-endpoint=127.0.0.1:1161 \ --v3-engine-id=090807060504030201 \ --v3-user=gromit --v3-auth-key=testkey564 \ --agent-udpv4-endpoint=127.0.0.1:1162 Scanning "/home/user/.snmpsim/variation" directory for variation modules... ... SNMPv3 EngineID: 0x010203040505060809 ------------------------------------------------------------------ SNMPv3 USM SecurityName: wallace SNMPv3 USM authentication key: testkey123, authentication protocol: MD5 Listening at UDP/IPv4 endpoint 127.0.0.1:1161, transport ID 1.3.6.1.6.1.1.0 ... SNMPv3 EngineID: 0x090807060504030201 ------------------------------------------------------------------ SNMPv3 USM SecurityName: gromit SNMPv3 USM authentication key: testkey564, authentication protocol: MD5 Listening at UDP/IPv4 endpoint 127.0.0.1:1162, transport ID 1.3.6.1.6.1.1.1 Likewise, to make particular SNMP Engine working with specific data directory, another, more specific, ``--data-dir`` option could be passed after the ``--v3-engine-id`` option. .. _running-options: Invocation Options ++++++++++++++++++ To make Simulator listening on SNMP-standard UDP port 161 on a UNIX system, you have to invoke it as root but in the same time have to specify some non-privileged UNIX user and group to switch into upon port allocation: .. code-block:: bash # snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:161 \ --process-user=simulator --process-group=simulator On UNIX systems Simulator can be run as a daemon. Make sure to re-direct its console output into syslog: .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:161 \ --process-user=simulator --process-group=simulator \ --daemonize --logging-method=syslog:local1:debug .. _logging-options: Logging Options +++++++++++++++ Most of the scripts shipped with the SNMP Simulator package can log to a remote syslog server over TCP or UDP: .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:161 \ --process-user=simulator --process-group=simulator \ --daemonize --logging-method=syslog:local1:debug:192.168.1.1:514:udp Finally, Simulator can simply log to a local log file: .. code-block:: bash $ snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1:161 \ --process-user=simulator --process-group=simulator \ --daemonize --logging-method=file:/var/log/snmpsimd.log snmpsim-1.2.2/docs/source/documentation/simulation-with-variation-modules.rst0000644000000000000000000011037013615410400024554 0ustar00 .. _simulation-with-variation-modules: Simulation with Variation Modules ================================= Without variation modules, simulated SNMP Agents are always static in terms of data returned to SNMP Managers. They are also read-only. By configuring particular OIDs or whole subtrees to be routed to variation modules, that allows you to make returned data changing over time. Another way of using variation modules is to gather data from some external source such as an SQL database or executed process or distant web-service. It's also possible to modify simulated values through SNMP SET operation and store modified values in a database so they will persist over Simulator restarts. Variation modules may be used for triggering events at other systems. For instance the ``notification`` module will send SNMP TRAP/INFORM SNMP messages to pre-configured SNMP Managers on SNMP SET request arrival to ``snmpsim-command-responder``. Finally, variation module API let you develop your own code in Python to fulfill your special needs and use your variation module with stock Simulator. .. _configuring-simulation-with-variation-modules: Configuring Variation Modules ----------------------------- To make use of a variation module you will have to edit existing or create a new data file adding reference to a variation module into the ``TAG`` field by means of :ref:`recording variation modules `. Remember :ref:`.snmprec file format ` is a sequence of lines having the ``OID|TAG|VALUE`` fields? With variation module in use, the ``TAG`` field complies to its own sub-format - ``TAG-ID[:MODULE-ID]``. Examples ++++++++ The following ``.snmprec`` file contents will invoke the ``writecache`` module and cast its returned values into ASN.1 ``OCTET STRING`` (4) and ``INTEGER`` (2) respectively: .. code-block:: bash 1.3.6.1.2.1.1.3.0|2:volatilecache|value=42 Whenever a subtree is routed to a variation module, ``TAG-ID`` part is left out as there might be no single type for all values within a subtree. Thus the empty ``TAG-ID`` sub-field serves as an indicator of a subtree. For example, the following data file will serve all OIDs under 1.3.6.1.2.1.1 prefix to the ``sql`` variation module: .. code-block:: bash 1.3.6.1.2.1.1|:sql|snmprec The value part is passed to variation module as-is. It is typically holds some module-specific configuration or initialization values. Another example: the following ``.snmprec`` line invokes the ``notification`` variation module instructing it to send SNMP INFORM message to SNMP manager at 127.0.01:162 over SNMPv3 with specific SNMP params: .. code-block:: bash 1.3.6.1.2.1.1.3.0|67:notification|version=3,user=usr-md5-des,\ authkey=authkey1,privkey=privkey1,host=127.0.0.1,ntftype=inform,\ trapoid=1.3.6.1.6.3.1.1.5.2,value=123456 The standard variation modules are installed into the Python site-packages directory. User can pass Simulator an alternative modules directory through the command line. Simulator will load and bootstrap all variation modules it finds. Some modules can accept initialization parameters (like database connection credentials) through ``snmpsim-command-responder --variation-module-options`` command-line parameter. For example, the following Simulator invocation will configure its ``sql`` variation module to use SQLite database (``sqlite3`` Python module) and ``/var/tmp/snmpsim.db`` database file: .. code-block:: bash $ snmpsim-command-responder --variation-module-options=sql:dbtype:sqlite3,\ database:/var/tmp/snmpsim.db If you are using multiple database connections or database types all through the ``sql`` variation module, you could refer to each module instance in ``.snmprec`` files through a so-called variation module alias. The following command-line runs Simulator with two instances of the ``involatilecache`` variation module (``dbA`` & ``dbB``) each instance using distinct database file for storing their persistent values: .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=writecache=dbA:file:/var/tmp/fileA.db \ --variation-module-options=writecache=dbB:file:/var/tmp/fileB.db The syntax for ``--variation-module-options=`` module configuration string is comma-separated list of semicolon-separated ``name:value`` pairs: .. code-block:: bash --variation-module-options=> With exception for the first semicolon (which is considered to be a part of module reference), the rest of separators could potentially intervene with values. In that case user could use a doubled or tripled separator tokens as an escaping aid: .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=writecache:file::C:\TEMP\fileA.db The same separator escaping method works for module options in ``.snmprec`` value field. The only difference is that ``.snmprec`` value syntax uses equal sign and commands as separators. .. _standard-variation-modules: Standard Variation Modules -------------------------- The following variation modules are shipped with SNMP Simulator: * The :ref:`numeric ` module produces a non-decreasing sequence of integers over time * The :ref:`notification ` module sends SNMP TRAP/INFORM messages to distant SNMP entity * The :ref:`writecache ` module accepts and stores (in memory/file) SNMP variable-bindings being modified through SNMP SET command * The :ref:`sql ` module reads/writes var-binds from/to a SQL database * The :ref:`redis ` module reads/writes var-binds from/to a no-SQL key-value store * The :ref:`delay ` module delays SNMP response by specified or random time * The :ref:`error ` module flag errors in SNMP response PDU * The :ref:`multiplex ` module uses a time series of .snmprec files picking one at a time. * The :ref:`subprocess ` module executes external process and puts its stdout values into response .. _variate-numeric: Numeric Module ++++++++++++++ The numeric module maintains and returns a changing in time integer value. The law and rate of changing is configurable. This module is per-OID stateful and configurable. The numeric module accepts the following comma-separated key=value parameters in ``.snmprec`` value field: * ``min`` - the minimum value ever stored and returned by this module. Default is ``0``. * ``max`` - the maximum value ever stored and returned by this module. Default is ``2\*\*32`` or ``2\*\*64`` (``Counter64`` type). * ``initial`` - initial value. Default is ``min``. * ``atime`` - if non-zero, uses current time for value generation, not Simulator uptime. * ``wrap`` - if zero, generated value will freeze when reaching ``max``. Otherwise generated value is reset to ``min``. * ``function`` - defines elapsed-time-to-generated-value relationship. Can be any of reasonably suitable mathematical function from the math module such as ``sin``, ``log``, ``pow`` etc. The only requirement is that used function accepts a single integer argument. Default is ``x = f(x)``. * ``rate`` - elapsed time scaling factor. Default is ``1``. * ``scale`` - function value scaling factor. Default is ``1``. * ``offset`` - constant value by which the return value increases on each invocation. Default is ``0``. * ``deviation`` - random deviation maximum. Default is ``0`` which means no deviation. * ``cumulative`` - if non-zero sums up previous value with the newly generated one. This is important when simulating ``COUNTER`` values. This module generates values by execution of the following formula: .. code-block:: python TIME = TIMENOW if atime else UPTIME v = function(TIME * rate) * scale + offset + RAND(-deviation, deviation) v = v + prev_v if cumulative else v Examples ~~~~~~~~ .. code-block:: bash # COUNTER object 1.3.6.1.2.1.2.2.1.13.1|65:numeric|scale=10,deviation=1,function=cos,cumulative=1,wrap=1 # GAUGE object 1.3.6.1.2.1.2.2.1.14.1|66:numeric|min=5,max=50,initial=25 You are welcome to try the ``numeric`` module in action at our online `public SNMP simulation service `_: .. code-block:: bash $ snmpget -v2c -c variation/virtualtable demo.pysnmp.com \ IF-MIB::ifLastChange.1 IF-MIB::ifInOctets.1 IF-MIB::ifLastChange.1 = Timeticks: (16808012) 1 day, 22:41:20.12 IF-MIB::ifInOctets.1 = Counter32: 30374688 The ``numeric`` module can be used for simulating ``INTEGER``, ``Counter32``, ``Counter64``, ``Gauge32``, ``TimeTicks`` objects. .. _variate-delay: Delay Module ++++++++++++ The delay module postpones SNMP request processing for specified number of milliseconds. Delay module accepts the following comma-separated ``key=value`` parameters in ``.snmprec`` value field: * ``value`` - holds the var-bind value to be included into SNMP response. In case of a string value containing commas, use the ``hexvalue`` key instead. * ``hexvalue`` - holds the var-bind value as a sequence of ASCII codes in hex form. Before putting it into var-bind, ``hexvalue`` contents will be converted into ASCII text. * ``wait`` - specifies for how many milliseconds to delay SNMP response. Default is ``500ms``. If the value exceeds ``999999``, request will never be answered (PDU will be dropped right away). * ``deviation`` - random delay deviation ranges (ms). Default is ``0`` which means no deviation. * ``vlist`` - a list of triples ``comparison:constant:delay`` to use on SET operation for choosing delay based on value supplied in request. The following comparison operators are supported: ``eq``, ``lt``, ``gt``. * ``tlist`` - a list of triples ``comparison:time:delay`` to use for choosing request delay based on time of day (seconds, UNIX time). The following comparison operators are supported: ``eq``, ``lt``, ``gt``. .. note:: Optional tag modifier in :ref:`.snmprec file ` is ignored by this variation module. Examples ~~~~~~~~ The following entry makes Simulator responding with an integer value of 6 delayed by 0.1sec +- 0.2 sec (negative delays are casted into zeros): .. code-block:: bash 1.3.6.1.2.1.2.2.1.3.1|2:delay|value=6,wait=100,deviation=200 Here the ``hexvalue`` takes shape of an ``OCTET STRING`` value ``0:12:79:62:f9:40`` delayed by exactly 0.8 sec: .. code-block:: bash 1.3.6.1.2.1.2.2.1.6.1|4:delay|hexvalue=00127962f940,wait=800 This entry drops PDU right away so the Manager will timed out: .. code-block:: bash 1.3.6.1.2.1.2.2.1.7.1|2:delay|wait=1000000 The following entry uses module default on GET/GETNEXT/GETBULK operations. However delays response by 0.1 sec if request value is exactly 0 and delays response by 1 sec on value equal to 1. .. code-block:: bash 1.3.6.1.2.1.2.2.1.8.1|2:delay|vlist=eq:0:100:eq:1:1000,value=1 The entry that follows uses module default on GET/GETNEXT/GETBULK operations, however, delays response by 0.001 sec if request value is exactly 100, uses module default on values >= 100 but <= 300 (0.5 sec), and drops request on values > 300: .. code-block:: bash 1.3.6.1.2.1.2.2.1.9.1|67:delay|vlist=lt:100:1:gt:300:1000000,value=150 The next example will simulate an unavailable agent past 01.04.2013 (1364860800 in UNIX time): .. code-block:: bash 1.3.6.1.2.1.2.2.1.10.1|67:delay|tlist=gt:1364860800:1000000,value=150 .. note:: Since SNMP Simulator is internally an asynchronous, single-thread application, any delayed response will block all concurrent requests processing as well. .. _variate-error: Error module ++++++++++++ The error module flags a configured error at SNMP response PDU. Error module accepts the following comma-separated key=value parameters in *.snmprec* value field: * *op* - either of *get*, *set* or *any* values to indicate SNMP operation that would trigger error response. Here *get* also enables GETNEXT and GETBULK operations. Default is *any*. * *value* - holds the var-bind value to be included into SNMP response. In case of a string value containing commas, use the *hexvalue* key instead. * *hexvalue* - holds the var-bind value as a sequence of ASCII codes in hex form. Before putting it into var-bind, hexvalue contents will be converted into ASCII text. * *status* - specifies error to be flagged. The following SNMP errors codes are supported: - *tooBig* - *noSuchName* (only makes sense for SNMP v1) - *badValue* - *readOnly* - *genError* - *noAccess* - *wrongType* - *wrongValue* - *noCreation* - *inconsistentValue* - *resourceUnavailable* - *commitFailed* - *undoFailed* - *authorizationError* - *notWritable* - *inconsistentName* - *noSuchObject* - *noSuchInstance* - *endOfMib* .. note:: When *tooBig* error is flagged, variable-bindings list is automatically reset to empty as per :RFC:`1905#section-4.2.2`. * *vlist* - a list of triples (comparison:constant:error) to use as an access list for SET values. The following comparison operators are supported: - *eq* - *lt* - *gt* The following SNMP errors are supported (case-insensitive): - *tooBig* - *noSuchName* (only makes sense for SNMP v1) - *badValue* - *readOnly* - *genError* - *noAccess* - *wrongType* - *wrongValue* - *noCreation* - *inconsistentValue* - *resourceUnavailable* - *commitFailed* - *undoFailed* - *authorizationError* - *notWritable* - *inconsistentName* - *noSuchObject* - *noSuchInstance* - *endOfMib* .. note:: Optional tag modifier in :ref:`.snmprec file ` is ignored by this variation module. Examples ~~~~~~~~ .. code-block:: bash 1.3.6.1.2.1.2.2.1.1.1|2:error|op=get,status=authorizationError,value=1 1.3.6.1.2.1.2.2.1.2.1|4:error|op=set,status=commitfailed,hexvalue=00127962f940 1.3.6.1.2.1.2.2.1.3.1|2:error|vlist=gt:2:wrongvalue,value=1 1.3.6.1.2.1.2.2.1.6.1|4:error|status=noaccess The first entry flags *authorizationError* on GET* and no error on SET. Second entry flags *commitfailed* on SET but responds without errors to GET*. Third entry fails with *wrongvalue* only on SET with values > 2. Finally, forth entry always flags *noaccess* error. .. _variate-writecache: Writecache module +++++++++++++++++ The *writecache* module lets you make particular OID at a *.snmprec* file writable via SNMP SET operation. The new value will be stored in Simulator process's memory or disk-based data store and communicated back on SNMP GET/GETNEXT/GETBULK operations. Data saved in disk-based data store will NOT be lost upon Simulator restart. Module initialization allows for passing a name of a database file to be used as a disk-based data store: .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=writecache:file:/tmp/shelves.db All modifed values will be kept and then subsequently used on a per-OID basis in the specified file. If data store file is not specified, the *writecache* module will keep all its data in [volatile] memory. The *writecache* module accepts the following comma-separated *key=value* parameters in *.snmprec* value field: * *value* - holds the var-bind value to be included into SNMP response. In case of a string value containing commas, use *hexvalue* instead. * *hexvalue* - holds the var-bind value as a sequence of ASCII codes in hex form. Before putting it into var-bind, hexvalue contents will be converted into ASCII text. * *vlist* - a list of triples *comparison:constant:error* to use as an access list for SET values. The following comparison operators are supported: - *eq* - *lt* - *gt* The following SNMP errors are supported (case-insensitive): - *genError* - *noAccess* - *wrongType* - *wrongValue* - *noCreation* - *inconsistentValue* - *resourceUnavailable* - *commitFailed* - *undoFailed* - *authorizationError* - *notWritable* - *inconsistentName* - *noSuchObject* - *noSuchInstance* - *endOfMib* .. note:: Optional tag modifier in :ref:`.snmprec file ` is ignored by this variation module. Examples ~~~~~~~~ .. code-block:: bash 1.3.6.1.2.1.1.3.0|2:writecache|value=42 In the above configuration, the initial value is 42 and can be modified by the *snmpset* command (assuming correct community name and Simulator is running locally). .. code-block:: bash $ snmpset -v2c -c community localhost 1.3.6.1.2.1.1.3.0 i 24 A more complex example involves using an access list. The following example allows only values of 1 and 2 to be SET: .. code-block:: bash 1.3.6.1.2.1.1.3.0|2:writecache|value=42,vlist=lt:1:wrongvalue:gt:2:wrongvalue Any other SET values will result in SNNP WrongValue error in response. .. note:: An attempt to SET a value of incompatible type will also result in error. .. _variate-multiplex: Multiplex module ++++++++++++++++ The multiplex module allows you to serve many snapshots for a single Agent picking just one snapshot at a time for answering SNMP request. That simulates a more natural Agent behaviour including the set of OIDs changing in time. This module is usually configured to serve an OID subtree in an *.snmprec* file entry. The multiplex module accepts the following comma-separated *key=value* parameters in *.snmprec* value field: * *dir* - path to *.snmprec* files directory. If path is not absolute, it is interpreted relative to Simulator's *--data-dir*. The *.snmprec* files names here must have numerical names ordered by time. * *period* - specifies for how long to use each *.snmprec* snapshot before switching to the next one. Default is 60 seconds. * *wrap* - if true, instructs the module to cycle through all available *.snmprec* files. If false, the system stops switching *.snmprec* files as it reaches the last one. Default is false. * *control* - defines a new OID to be used for switching *.snmprec* file via SNMP SET command. Examples ~~~~~~~~ .. code-block:: bash 1.3.6.1.2.1.2|:multiplex|dir=variation/snapshots,period=10.0 1.3.6.1.3.1.1|4|snmprec The variation/snapshots/ directory contents is a name-ordered collection of *.snmprec* files: .. code-block:: bash $ ls -l /usr/local/share/snmpsim/data/variation/snapshots -rw-r--r-- 1 root staff 3145 Mar 30 22:52 00000.snmprec -rw-r--r-- 1 root staff 3145 Mar 30 22:52 00001.snmprec -rw-r--r-- 1 root staff 3145 Mar 30 22:52 00002.snmprec ... Simulator can use each of these files only once through its configured time series. To make it cycling over them, use *wrap* option. The *.snmprec* files served by the multiplex module can not include references to variation modules. In cases when automatic, time-based *.snmprec* multiplexing is not applicable for simulation purposes, *.snmprec* selection can be configured:

.. code-block:: bash 1.3.6.1.2.1.2|:multiplex|dir=variation/snapshots,control=1.3.6.1.2.1.2.999 The following command will switch multiplex module to use the first *.snmprec* file for simulation: .. code-block:: bash $ snmpset -v2c -c variation/multiplex localhost 1.3.6.1.2.1.2.999 i 0 Whenever *control* OID is present in multiplex module options, the time-based multiplexing will not be used. .. _variate-subprocess: Subprocess module +++++++++++++++++ The *subprocess* module can be used to execute an external program passing it request data and using its stdout output as a response value. Module invocation supports passing a *shell* option which (if true) makes Simulator using shell for subprocess invocation. Default is True on Windows platform and False on all others. .. warning:: With *shell=True*, UNIX shell gets into the pipeline what compromises security. .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=subprocess:shell:1 Value part of *.snmprec* line should contain space-separated path to external program executable followed by optional command-line parameters. SNMP request parameters could be passed to the program to be executed by means of macro variables. With subprocess module, macro variables names always carry '@' sign at front and back (e.g. @MACRO@). Macros ~~~~~~ * *@DATAFILE@* - resolves into the *.snmprec* file selected by SNMP Simulator for serving current request * *@OID@* - resolves into an OID of *.snmprec* line selected for serving current request * *@TAG@* - resolves into the component of *.snmprec* line selected for serving current request * *@ORIGOID@* - resolves into currently processed var-bind OID * *@ORIGTAG@* - resolves into value type of currently processed var-bind * *@ORIGVALUE@* - resolves into value of currently processed var-bind * *@SETFLAG@* - resolves into '1' on SNMP SET, '0' otherwise * *@NEXTFLAG@* - resolves into '1' on SNMP GETNEXT/GETBULK, '0' otherwise * *@SUBTREEFLAG@* - resolves into '1' if the *.snmprec* file line selected for processing current request serves a subtree of OIDs rather than a single specific OID * *@TRANSPORTDOMAIN@* - SNMP transport domain as an OID. It has a one-to-one relationship with local interfaces Simulator is configured to listen at * *@TRANSPORTADDRESS@* - peer transport address * *@SECURITYMODEL@* - SNMPv3 Security Model * *@SECURITYNAME@* - SNMPv3 Security Name * *@SECURITYLEVEL@* - SNMPv3 Security Level * *@CONTEXTNAME@* - SNMPv3 Context Name Examples ~~~~~~~~ .. code-block:: bash 1.3.6.1.2.1.1.1.0|4:subprocess|echo SNMP Context is @DATAFILE@, received \ request for @ORIGOID@, matched @OID@, received tag/value \ "@ORIGTAG@"/"@ORIGVALUE@", would return value tagged @TAG@, SET request \ flag is @SETFLAG@, next flag is @NEXTFLAG@, subtree flag is \ @SUBTREEFLAG@ 1.3.6.1.2.1.1.3.0|2:subprocess|date +%s The first entry simply packs all current macro variables contents as a response string my printing them to stdout with echo, second entry invokes the UNIX date command instructing it to report elapsed UNIX epoch time. Note *.snmprec* tag values -- executed program's stdout will be casted into appropriate type depending of tag indication. .. _variate-notification: Notification module +++++++++++++++++++ The *notification* module can send SNMP TRAP/INFORM notifications to distant SNMP engines by way of serving SNMP request sent to Simulator. In other words, SNMP message sent to Simulator can trigger sending TRAP/INFORM message to pre-configured targets. .. note:: No new process is created when sending SNMP notification -- *snmpsimd*'s own SNMP engine is reused. The *notification* module accepts the following comma-separated *key=value* parameters in *.snmprec* value field: * *value* - holds the variable-bindings value to be included into SNMP response message. * *op* - either of *get*, *set* or *any* values to indicate SNMP operation that would trigger notification. Here *get* also enables GETNEXT and GETBULK operations. Default is *set*. * *vlist* - a list of pairs *comparison:constant* to use as event triggering criteria to be compared against SET values. The following comparisons are supported: *eq*, *lt*, *gt*. * *version* - SNMP version to use (1,2c,3). * *ntftype* - indicates notification type. Either *trap* or *inform*. * *community* - SNMP community name. For v1, v2c only. Default is *public*. * *trapoid* - SNMP TRAP PDU element. Default is *coldStar*. * *uptime* - SNMP TRAP PDU element. Default is local SNMP engine uptime. * *agentaddress* - SNMP TRAP PDU element. For v1 only. Default is local SNMP engine address. * *enterprise* - SNMP TRAP PDU element. For v1 only. * *user* - USM username. For v3 only. * *authproto* - USM auth protocol. For v3 only. Either *md5* or *sha*. Default is *md5*. * *authkey* - USM auth key. For v3 only. * *privproto* - USM encryption protocol. For v3 only. Either *des* or *aes*. Default is *des*. * *privkey* - USM encryption key. For v3 only. * *proto* - transport protocol. Either *udp* or *udp6*. Default is *udp*. * *host*- hostname or network address to send notification to. * *port* - UDP port to send notification to. Default is 162. * *varbinds* - a semicolon-separated list of *OID:TAG:VALUE:OID:TAG:VALUE...* of var-binds to add into SNMP TRAP PDU. The following *TAG* values are recognized: - *s* - OctetString (expects character string) - *h* - OctetString (expects hex string) - *i* - Integer32 - *o* - ObjectName - *a* - IpAddress - *u* - Unsigned32 - *g* - Gauge32 - *t* - TimeTicks - *b* - Bits - *I* - Counter64 .. note:: Optional tag modifier in :ref:`.snmprec file ` is ignored by this variation module. Examples ~~~~~~~~ The following three *.snmprec* lines will send SNMP v1, v2c and v3 notifications whenever Simulator is processing GET* and/or SET request for configured OIDs: .. code-block:: bash 1.3.6.1.2.1.1.1.0|4:notification|op=get,version=1,community=public,\ proto=udp,host=127.0.0.1,port=162,ntftype=trap,\ trapoid=1.3.6.1.4.1.20408.4.1.1.2.0.432,uptime=12345,agentaddress=127.0.0.1,\ enterprise=1.3.6.1.4.1.20408.4.1.1.2,\ varbinds=1.3.6.1.2.1.1.1.0:s:snmpsim agent:1.3.6.1.2.1.1.3.0:i:42,\ value=SNMPv1 trap sender 1.3.6.1.2.1.1.2.0|6:notification|op=set,version=2c,community=public,\ host=127.0.0.1,ntftype=trap,trapoid=1.3.6.1.6.3.1.1.5.1,\ varbinds=1.3.6.1.2.1.1.1.0:s:snmpsim agent:1.3.6.1.2.1.1.3.0:i:42,\ value=1.3.6.1.1.1 1.3.6.1.2.1.1.3.0|67:notification|version=3,user=usr-md5-des,authkey=authkey1,\ privkey=privkey1,host=127.0.0.1,ntftype=inform,trapoid=1.3.6.1.6.3.1.1.5.2,\ value=123456 .. note:: The delivery status of INFORM notifications is not communicated back to the SNMP Manager working with Simulator. .. _variate-sql: SQL module ++++++++++ The *sql* module lets you keep subtrees of OIDs and their values in a relational database. All SNMP operations are supported including transactional SET. Module invocation requires passing database type (sqlite3, psycopg2, MySQL and any other compliant to `Python DB-API `_ and importable as a Python module) and connect string which is database dependant. Besides DB-specific connect string key-value parameters, sql module supports the following comma-separated key:value options whilst running in recording mode: * *dbtype* - SQL DBMS type in form of Python DPI API-compliant module. It will be imported into Python as specified. * *dbtable* - Default SQL table name to use for storing recorded snapshot. It is used if table name is not specified in *.snmprec* file. * *isolationlevel* - SQL transaction `isolation level `_. Allowed values are: - *0* - READ UNCOMMITTED - *1* - READ COMMITTED - *2* - REPEATABLE READ - *3* - SERIALIZABLE Default is READ COMMITTED. Database connection ~~~~~~~~~~~~~~~~~~~ For SQLite database invocation use the following command: .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=sql:dbtype:sqlite3,database:/var/tmp/sqlite.db To use a MySQL database for OID/value storage, the following Simulator invocation would work: .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=sql:dbtype:mysql.connector,\ host:127.0.0.1,port:3306,user:snmpsim,password:snmpsim,database:snmpsim assuming you have the `MySQL Connector/Python driver `_ is installed on the SNMP Simulator machine and a MySQL server running at 127.0.0.1 with MySQL user/password snmpsim/snmpsim having full access to a database *snmpsim* Another variation of MySQL server installation setup on a UNIX system employs UNIX domain socket for client-server communication. In that case the following command-line for *.snmprec* might work: .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=sql:dbtype:mysql.connector,\ unix_socket:/var/run/mysql/mysql.sock,user:snmpsim,\ password:snmpsim,database:snmpsim Alternatively, the `MySQL for Python `_ package can be used for Simulator to MySQL connection: .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=sql:dbtype:MySQLdb,host:127.0.0.1,\ port:3306,user:snmpsim,passwd:snmpsim,db:snmpsim If you wish to use `PostgresSQL `_ database for OID/value storage, the following command line will do the job: .. code-block:: bash $ snmpsim-command-responder --variation-module-options=sql:dbtype:psycopg2, user:snmpsim,password:snmpsim,database:snmpsim assuming you have the `Psycopg Python adapter `_ is installed on the SNMP Simulator machine and a PostgreSQL server running locally (accessed through default UNIX domain socket) with PostgreSQL user/password snmpsim/snmpsim having full access to a database *snmpsim*. Simulation data configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The *.snmprec* value is expected to hold database table name to keep all OID-value pairs served within selected *.snmprec* line. This table can either be created automatically whenever *sql* module is invoked in :ref:`recording mode ` or can be created and populated by hand. In the latter case table layout should be as follows: .. code-block:: bash CREATE TABLE (oid text, tag text, value text, maxaccess text) The most usual setup is to keep many OID-value pairs in a database table referred to by a *.snmprec* line serving a subtree of OIDs: .. code-block:: bash 1.3.6.1.2.1.1|:sql|snmprec In the above case all OIDs under 1.3.6.1.2.1.1 prefix will be handled by a sql module using 'snmprec' table. .. note:: To make SQL's ORDER BY clause working with OIDs, each sub-OID stored in the database (in case of manual database population) must be left-padded with a good bunch of spaces (each sub-OID width is 10 characters). .. _variate-redis: Redis module ++++++++++++ The *redis* module lets you keep subtrees of OIDs and their values in a no-SQL key-value store. Besides complete SNMP operations support, Redis server-side Lua scripts are also supported at both variation and :ref:`recording ` stages. For redis variation module to work you must also have the `redis-py `_ Python module installed on your system. Module invocation requires passing Redis database connection string. The following parameters are supported: * *host* - Redis hostname or IP address. * *port* - Redis TCP port the server is listening on. * *unix_socket* - UNIX domain socket Redis server is listening on. * *db* - Redis database number. * *password* - Redis database admission password. .. code-block:: bash $ snmpsim-command-responder \ --variation-module-options=redis:host:127.0.0.1,port:6379,db:0 SNMP variable-bindings recorded by Simulator in a single recording session is placed into a dedicated key namespace called "key space". This allows for keeping many versions of the same oid-value pair either belonging to different Agents or recorded at different times. These key spaces are organized by pre-pending a session-unique "key space" to each key put into Redis. Simulator keeps recorded SNMP var-binds in three types of Redis data structures: * Redis `String `_ where each key is composed from key space and an OID joint with a dash *-*. Values are SNMP data type tag and value in :ref:`snmprec format `. This is where simulation data is stored. * Redis `LIST `_ object keyed *|oids_ordering* where each element is a key from the String above. The purpose of this structure is to order OIDs what is important for serving SNMP GETNEXT/GETBULK queries. * Redis `LIST `_ object keyed ** where each element is a from the LIST above. The purpose of this structure is to consolidate many key spaces into a sequence to form simulation data time series and ease switching key spaces during simulation. The data structure above can be created manually or automatically whenever redis module is invoked in :ref:`recording mode `. .. note:: To make string-typed OIDs comparable, sub-OIDs of original OIDs must be left-padded with a good bunch of spaces (up to 9) so that 1.3.6 will become ' 1. 3. 6'. The .snmprec value is expected to hold more Redis database access parameters, specific to OID-value pairs served within selected *.snmprec* line. * *key-spaces-id* - Redis key used to store a sequence of key-spaces referring to oid-value collections used for simulation. * *period* - number of seconds to switch from one key-space to another within the key-spaces-id list. * *evalsha* - Redis server side `Lua script `_ to use for accessing oid-value pairs stored in Redis. If this option is not given, bare Redis GET/SET commands will be used instead. Examples ~~~~~~~~ The most usual setup is to keep many OID-value pairs in a Redis database referred to by a *.snmprec* line serving a subtree of OIDs: .. code-block:: bash 1.3.6.1.2.1.1|:redis|key-spaces-id=1234 In the above case all OIDs under 1.3.6.1.2.1.1 prefix will be handled by redis module using key spaces stored in "1234" Redis list. For example, the "1234" keyed list can hold the following key spaces: ["4321", "4322", "4323"]. Then the following keys can be stored for 1.3.6.1.2.1.1.3.0 OID: .. code-block:: bash "4321-<9 spaces>.1<9 spaces>.3<9 spaces>.6 ... <9 spaces>.3<9 spaces>.0" = "67|812981" "4322-<9 spaces>.1<9 spaces>.3<9 spaces>.6 ... <9 spaces>.3<9 spaces>.0" = "67|813181" "4323-<9 spaces>.1<9 spaces>.3<9 spaces>.6 ... <9 spaces>.3<9 spaces>.0" = "67|814233" If *period* parameter is passed through the *.snmprec* record, Simulator will automatically change key space every *period* seconds when gathering data for SNMP responses. The *key-spaces-id* Redis list can also be manipulated by an external application at any moment for the purpose of switching key spaces while Simulator is running. Simulated values can also be modified on-the-fly by an external application. However, when adding/removing OIDs, not just modifying simulation data, care must be taken to keep the -oids_ordering list ordered and synchronized with the collection of -OID keys being used for storing simulation data. Besides using an external application for modifying simulation data, custom `Lua script `_ can be used for dynamic response and/or stored data modification. For example, the following *.snmprec* entry will invoke server-side Lua script stored under the name of "d94bf1756cda4f55bac9fe9bb872f" when getting/setting Redis keys: .. code-block:: bash 1.3.6.1.2.1.1|:redis|key-spaces-id=1234,evalsha=d94bf1756cda4f55bac9fe9bb872f Here's an example Lua script, carrying no additional logic, stored at Redis server using the `SCRIPT LOAD `_ Redis command: .. code-block:: bash $ redis-cli 127.0.0.1:6379> script load " if table.getn(ARGV) > 0 then return redis.call('set', KEYS[1], ARGV[1]) else return redis.call('get', KEYS[1]) end " "d94bf1756cda4f55bac9fe9bb872f" 127.0.0.1:6379> SNMP Simulator will perform SET/GET operations through its *evalsha* script like this: .. code-block:: bash $ redis-cli 127.0.0.1:6379> evalsha "d94bf1756cda4f55bac9fe9bb872f" 1 "4321|1. 1. 6. 1. 2. 1. 2. 1. 2. 0" "4|linksys router" 127.0.0.1:6379> evalsha "d94bf1756cda4f55bac9fe9bb872f" 1 "4321|1. 1. 6. 1. 2. 1. 2. 1. 2. 0" "4|linksys router" 127.0.0.1:6379> A much more complex Lua scripts could be written to dynamically modify other parts of the database, sending messages to other Redis-backed applications through Redis's `Publish/Subscribe `_ facility. Writing variation modules ------------------------- Whenever you consider coding your own variation module, take a look at the existing ones. The API is very simple - it basically takes three Python functions (init, process, shutdown) where process() is expected to return a var-bind pair per each invocation. snmpsim-1.2.2/docs/source/documentation/tips-and-tricks.rst0000644000000000000000000001372213615410400020776 0ustar00 Tips and tricks =============== Here we have a handful of observations and advices based on user feedback and field deployment. .. _tips-multiple-instances: Multiple instances ------------------ SNMP Simulator is designed to simulate tens of thousands of SNMP Agents at once. However, it is more optimized for large number of simulated devices than to high sustainability under high load. The reason is that, internally, SNMP Simulator is a single-threaded application meaning it can only process a single request at once. On top of that, some variation modules may bring additional delay to request processing what may cause subsequent requests to build up in input queue and contribute to increasing latency. A simple receipt aimed at maximizing throughput and minimizing latency is to run multiple instances of the *snmpsim-command-responder* bound to distinct IP interfaces or ports what effectively makes SNMP Simulator executing multiple requests at once for as long as they are sent towards different *snmpsim-command-responder* instances. Here is how we invoke two *snmpsim-command-responder* instances on different IP interfaces serving the same set of data files: .. code-block:: bash # snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.1 \ --agent-udpv4-endpoint=127.0.0.2 \ --transport-id-offset=1 \ --data-dir=/usr/local/share/snmpsim/data \ --cache-dir=/var/tmp/snmpsim-A/cache \ --process-user=nobody --process-group=nogroup \ --daemonize && # snmpsim-command-responder --agent-udpv4-endpoint=127.0.0.3 \ --agent-udpv4-endpoint=127.0.0.4 \ --transport-id-offset=3 \ --data-dir=/usr/local/share/snmpsim/data \ --cache-dir=/var/tmp/snmpsim-B/cache \ --process-user=nobody --process-group=nogroup \ --daemonize && Several things to note here: * The *snmpsim-command-responder* instances share common data directory however use their own, dedicated cache directories. * Each *snmpsim-command-responder* instance is listening on multiple IP interfaces (for the purpose of :ref:`address-based ` simulation) * The transport ID's are configured explicitly making the following *--data-dir* layout possible: .. code-block:: bash $ tree data data |-- public | |-- 1.3.6.1.6.1.1.1.snmprec |-- public | |-- 1.3.6.1.6.1.1.2.snmprec |-- public | |-- 1.3.6.1.6.1.1.3.snmprec |-- public |-- 1.3.6.1.6.1.1.4.snmprec The end result is that each simulated Agent is available by a dedicated IP address (represented by a transport ID) and common SNMPv1/v2c community name *public*. .. note:: To make use of IP address based Agent addressing feature the *--v2c-arch* mode is used. .. _tips-file-based-configuration: File-based configuration ------------------------ The above setup can be scaled to as many IP interfaces as you can bring up on your system. A really large number of IP interfaces might exceed the length of the command-line. In that case it's advised to use the *--args-from-file=*; option to pass local IP addresses for Simulator to listen on. .. code-block:: bash # head ips.txt --agent-udpv4-endpoint=127.0.0.1:161 --agent-udpv4-endpoint=127.0.0.2:161 --agent-udpv4-endpoint=127.0.0.3:161 ... --agent-udpv4-endpoint=127.0.1.254:161 # snmpsim-command-responder --args-from-file=ips.txt \ --data-dir=/usr/local/share/snmpsim/data \ --v2c-arch \ --process-user=nobody --process-group=nogroup \ --daemonize & .. note:: Other parameters can also be present in the file passed to Simulator with the *--args-from-file* option. For the :ref:`address-based ` simulation it makes to design your *--data-dir* layout matching transport ID's of the addresses listed in the *ips.txt* file as shown above. .. _tips-listing-simulated-devices: Listing simulated agents ------------------------ When simulating a large pool of devices or if your Simulator runs on a distant machine, it is convenient to have a directory of all simulated devices and their community/context names. Simulator maintains this information within its internal, dedicated SNMP context 'index': .. code-block:: bash $ snmpwalk -On -v2c -c index localhost:1161 .1.3.6 .1.3.6.1.4.1.20408.999.1.1.1 = STRING: "./data/127.0.0.1@public.snmprec" .1.3.6.1.4.1.20408.999.1.2.1 = STRING: "data/127.0.0.1@public" .1.3.6.1.4.1.20408.999.1.3.1 = STRING: "9535d96c66759362b3521f4e273fc749" or .. code-block:: bash $ snmpwalk -O n -l authPriv -u simulator -A auctoritas -X privatus -n index localhost:1161 .1.3.6 .1.3.6.1.4.1.20408.999.1.1.1 = STRING: "./data/127.0.0.1@public.snmprec" .1.3.6.1.4.1.20408.999.1.2.1 = STRING: "data/127.0.0.1@public" .1.3.6.1.4.1.20408.999.1.3.1 = STRING: "9535d96c66759362b3521f4e273fc749" Where first column holds device file path, second - community string, and third - SNMPv3 context name. .. _tips-faster-response: Faster operation ---------------- The SNMPv3 architecture is inherently computationally heavy what makes SNMPv3 operations slower that SNMPv1/v2c ones. The SNMP Simulator can run faster when it uses a much lighter and lower-level SNMPv1/v2c architecture at the expense of not supporting v3 operations. Invoke *snmpsim-command-responder-lite* tool to leverage the lightweight implementation. .. _tips-quick-startup: Quicker startup --------------- When Simulator runs over thousands of device files, startup may take time (tens of seconds). Most of it goes into configuring SNMPv1/v2c credentials into SNMPv3 engine so startup time can be dramatically reduced by either using the lite version of command responder tool or by turning off SNMPv1/v2c configuration at SNMPv3 engine with *--v3-only* command-line flag of full version of command responder. snmpsim-1.2.2/docs/source/includes/_links.rst0000644000000000000000000000254513615410400016177 0ustar00.. toctree:: .. _LeXtudio Inc.: https://www.lextudio.com .. _PySNMP PyPI package: http://pypi.python.org/pypi/pysnmp .. _PySNMP GitHub repository: https://github.com/lextudio/pysnmp .. _GitHub issue: https://github.com/lextudio/pysnmp/issues/new .. _Stack Overflow: http://stackoverflow.com/questions/tagged/pysnmp .. _demo.pysnmp.com: https://www.pysnmp.com/snmp-simulation-service# .. _mibs.pysnmp.com: https://mibs.pysnmp.com/ .. _PySMI: https://www.pysnmp.com/pysmi/ .. _SNMP Proxy Forwarder: https://www.pysnmp.com/snmpfwd/ .. _snmpsim: https://www.pysnmp.com/snmpsim/ .. _PyASN1: https://pyasn1.readthedocs.io/ .. _mibdump: https://www.pysnmp.com/pysmi/docs/mibdump .. _command line tools: https://www.pysnmp.com/snmpclitools/ .. _Python: https://www.python.org/ .. _SNMP: https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol .. _PSF: https://www.python.org/psf/ .. _ASN.1: https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One .. _RFC3413: https://www.ietf.org/rfc/rfc3413.txt .. _RFC3418: https://www.ietf.org/rfc/rfc3418.txt .. _pyenv: https://github.com/pyenv/pyenv .. _pyenv-win: https://github.com/pyenv-win/pyenv-win .. _Wireshark: https://www.wireshark.org/ .. _tcpdump: https://www.tcpdump.org/ .. _Net-SNMP: http://www.net-snmp.org/ .. _Support Options: https://www.pysnmp.com/support .. _SNMP Simulator Data: https://www.pysnmp.com/snmpsim-data/ snmpsim-1.2.2/scripts/README.md0000644000000000000000000000007513615410400013072 0ustar00# pysnmp-scripts Scripts for PySNMP development environments snmpsim-1.2.2/snmpsim/__init__.py0000644000000000000000000000010113615410400013711 0ustar00# http://www.python.org/dev/peps/pep-0396/ __version__ = "1.2.2" snmpsim-1.2.2/snmpsim/confdir.py0000644000000000000000000000415313615410400013611 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # import os import sys import tempfile if sys.platform[:3] == "win": variation = [ os.path.join(os.environ["HOMEPATH"], "SNMP Simulator", "Variation"), os.path.join(os.environ["APPDATA"], "SNMP Simulator", "Variation"), os.path.join(os.environ["PROGRAMFILES"], "SNMP Simulator", "Variation"), os.path.join(os.path.split(__file__)[0], "variation"), ] data = [ os.path.join(os.environ["HOMEPATH"], "SNMP Simulator", "Data"), os.path.join(os.environ["APPDATA"], "SNMP Simulator", "Data"), os.path.join(os.environ["PROGRAMFILES"], "SNMP Simulator", "Data"), os.path.join(os.path.split(__file__)[0], "data"), ] elif sys.platform == "darwin": variation = [ os.path.join(os.environ["HOME"], ".snmpsim", "variation"), os.path.join("/", "usr", "local", "share", "snmpsim", "variation"), os.path.join(sys.prefix, "snmpsim", "variation"), os.path.join(sys.prefix, "share", "snmpsim", "variation"), os.path.join(os.path.split(__file__)[0], "variation"), ] data = [ os.path.join(os.environ["HOME"], ".snmpsim", "data"), os.path.join("/", "usr", "local", "share", "snmpsim", "data"), os.path.join(sys.prefix, "snmpsim", "data"), os.path.join(sys.prefix, "share", "snmpsim", "data"), os.path.join(os.path.split(__file__)[0], "data"), ] else: variation = [ os.path.join(os.environ["HOME"], ".snmpsim", "variation"), os.path.join(sys.prefix, "snmpsim", "variation"), os.path.join(sys.prefix, "share", "snmpsim", "variation"), os.path.join(os.path.split(__file__)[0], "variation"), ] data = [ os.path.join(os.environ["HOME"], ".snmpsim", "data"), os.path.join(sys.prefix, "snmpsim", "data"), os.path.join(sys.prefix, "share", "snmpsim", "data"), os.path.join(os.path.split(__file__)[0], "data"), ] cache = os.path.join(tempfile.gettempdir(), "snmpsim") snmpsim-1.2.2/snmpsim/controller.py0000644000000000000000000001151413615410400014347 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP Agent Simulator # from pysnmp.proto import rfc1902 from pysnmp.smi import exval, indices from pysnmp.carrier.asyncio.dgram import udp from pysnmp.carrier.asyncio.dgram import udp6 from snmpsim import datafile from snmpsim import log class MibInstrumController: """Lightweight MIB instrumentation (API-compatible with pysnmp's)""" def __init__(self, data_file): self._data_file = data_file def __str__(self): return str(self._data_file) def _get_call_context(self, next_flag=False, set_flag=False, **context): if context is None: return {"nextFlag": next_flag, "setFlag": set_flag} snmp_engine = context["snmpEngine"] # we injected snmpEngine object earlier # this API is first introduced in pysnmp 4.2.6 execCtx = snmp_engine.observer.get_execution_context( "rfc3412.receiveMessage:request" ) ( transport_domain, transport_address, security_model, security_name, security_level, context_engine_id, context_name, pdu_type, ) = ( execCtx["transportDomain"], execCtx["transportAddress"], execCtx["securityModel"], execCtx["securityName"], execCtx["securityLevel"], execCtx["contextEngineId"], execCtx["contextName"], execCtx["pdu"].__class__.__name__, ) if isinstance(transport_address, udp.UdpTransportAddress): transport_protocol = "udpv4" elif isinstance(transport_address, udp6.Udp6TransportAddress): transport_protocol = "udpv6" else: transport_protocol = "unknown" log.info( "SNMP EngineID %s, transportDomain %s, transportAddress %s, " "securityModel %s, securityName %s, securityLevel " "%s" % ( hasattr(snmp_engine, "snmpEngineID") and snmp_engine.snmpEngineID.prettyPrint() or "", transport_domain, transport_address, security_model, security_name, security_level, ) ) return { "snmpEngine": snmp_engine, "transportDomain": rfc1902.ObjectIdentifier(transport_domain), "transportAddress": transport_address, # "transportEndpoint": transport_address.getLocalAddress(), "transportProtocol": transport_protocol, "securityModel": security_model, "securityName": security_name, "securityLevel": security_level, "contextEngineId": context_engine_id, "contextName": context_name, "pduType": pdu_type, "nextFlag": next_flag, "setFlag": set_flag, } def read_variables(self, *var_binds, **context): return self._data_file.process_var_binds( var_binds, **self._get_call_context(False, False, **context) ) def read_next_variables(self, *var_binds, **context): return self._data_file.process_var_binds( var_binds, **self._get_call_context(True, False, **context) ) def write_variables(self, *var_binds, **context): return self._data_file.process_var_binds( var_binds, **self._get_call_context(False, True, **context) ) class DataIndexInstrumController: """Data files index as a MIB instrumentation in a dedicated SNMP context""" index_sub_oid = (1,) def __init__(self, base_oid=(1, 3, 6, 1, 4, 1, 20408, 999)): self._db = indices.OidOrderedDict() self._index_oid = base_oid + self.index_sub_oid self._idx = 1 def __str__(self): return " controller" def read_variables(self, var_binds, **context): return [(vb[0], self._db.get(vb[0], exval.noSuchInstance)) for vb in var_binds] def _get_next_val(self, key, default): try: key = self._db.nextKey(key) except KeyError: return key, default else: return key, self._db[key] def read_next_variables(self, var_binds, **context): return [self._get_next_val(vb[0], exval.endOfMib) for vb in var_binds] def write_variables(self, var_binds, **context): return [(vb[0], exval.noSuchInstance) for vb in var_binds] def add_data_file(self, *args): for idx in range(len(args)): self._db[self._index_oid + (idx + 1, self._idx)] = rfc1902.OctetString( args[idx] ) self._idx += 1 MIB_CONTROLLERS = {datafile.DataFile.layout: MibInstrumController} snmpsim-1.2.2/snmpsim/daemon.py0000644000000000000000000001275613615410400013440 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # import sys from snmpsim import error if sys.platform[:3] == "win": def daemonize(pidfile): raise error.SnmpsimError("Windows is not inhabited with daemons!") class PrivilegesOf: """Context manager performing nothing on Windows""" def __init__(self, *args, **kwargs): pass def __enter__(self): pass def __exit__(self, *args): pass else: import os import pwd import grp import atexit import signal import tempfile def daemonize(pidfile): try: pid = os.fork() if pid > 0: # exit first parent os._exit(0) except OSError as exc: raise error.SnmpsimError("ERROR: fork #1 failed: %s" % exc) # decouple from parent environment try: os.chdir("/") os.setsid() except OSError: pass os.umask(0) # do second fork try: pid = os.fork() if pid > 0: # exit from second parent os._exit(0) except OSError as exc: raise error.SnmpsimError("ERROR: fork #2 failed: %s" % exc) def signal_cb(s, f): raise KeyboardInterrupt for s in signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT: signal.signal(s, signal_cb) # write pidfile def atexit_cb(): try: if pidfile: os.remove(pidfile) except OSError: pass atexit.register(atexit_cb) try: if pidfile: fd, nm = tempfile.mkstemp(dir=os.path.dirname(pidfile)) os.write(fd, ("%d\n" % os.getpid()).encode("utf-8")) os.close(fd) os.rename(nm, pidfile) except Exception as exc: raise error.SnmpsimError(f"Failed to create PID file {pidfile}: {exc}") # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() si = open(os.devnull) so = open(os.devnull, "a+") se = open(os.devnull, "a+") os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) class PrivilegesOf: """Context manager executing under reduced privileges""" def __init__(self, uname, gname, final=False): self._uname = uname self._gname = gname self._final = final self._olduid = self._oldgid = None def __enter__(self): if os.getenv("SNMPSIM_ALLOW_ROOT") == "true": return if os.getuid() != 0: if self._uname or self._gname: try: pw_name = pwd.getpwnam(self._uname).pw_name gr_name = grp.getgrnam(self._gname).gr_name except Exception as exc: raise error.SnmpsimError( "getpwnam()/getgrnam() failed for %s/%s: " "%s" % (self._uname, self._gname, exc) ) if self._uname != pw_name or self._gname != gr_name: raise error.SnmpsimError( "Process is running under different UID/GID" ) else: return else: if not self._uname or not self._gname: raise error.SnmpsimError( "Must drop privileges to a non-privileged user&group" ) try: runningUid = pwd.getpwnam(self._uname).pw_uid runningGid = grp.getgrnam(self._gname).gr_gid except Exception as exc: raise error.SnmpsimError( "getpwnam()/getgrnam() failed for %s/%s: " "%s" % (self._uname, self._gname, exc) ) try: os.setgroups([]) except Exception as exc: raise error.SnmpsimError("setgroups() failed: %s" % exc) try: if self._final: os.setgid(runningGid) os.setuid(runningUid) else: self._olduid = os.getuid() self._oldgid = os.getgid() os.setegid(runningGid) os.seteuid(runningUid) except Exception as exc: raise error.SnmpsimError( "%s failed for %s/%s: %s" % ( self._final and "setgid()/setuid()" or "setegid()/seteuid()", runningGid, runningUid, exc, ) ) os.umask(63) # 0077 def __exit__(self, *args): if self._olduid is None or self._oldgid is None: return try: os.setegid(self._oldgid) os.seteuid(self._olduid) except Exception as exc: raise error.SnmpsimError( "setegid()/seteuid() failed for %s/%s: %s" % (self._oldgid, self._olduid, exc) ) snmpsim-1.2.2/snmpsim/datafile.py0000644000000000000000000003005513615410400013736 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # Simulation data file management tools # import os import stat from pyasn1.type import univ from pysnmp.carrier.asyncio.dgram import udp from pysnmp.carrier.asyncio.dgram import udp6 from pysnmp.proto import rfc1902 from pysnmp.smi import exval from pysnmp.smi.error import MibOperationError from snmpsim import log from snmpsim import variation from snmpsim.error import NoDataNotification from snmpsim.error import SnmpsimError from snmpsim.record.search.database import RecordIndex from snmpsim.record.search.file import get_record from snmpsim.record.search.file import search_record_by_oid from snmpsim.reporting.manager import ReportingManager SELF_LABEL = "self" class AbstractLayout: layout = "?" class DataFile(AbstractLayout): layout = "text" opened_queue = [] max_queue_entries = 31 # max number of open text and index files def __init__(self, textFile, textParser, variationModules): self._record_index = RecordIndex(textFile, textParser) self._text_parser = textParser self._text_file = textFile self._variation_modules = variationModules def index_text(self, forceIndexBuild=False, validateData=False): self._record_index.create(forceIndexBuild, validateData) return self def close(self): self._record_index.close() def get_handles(self): if not self._record_index.is_open(): if len(DataFile.opened_queue) > self.max_queue_entries: log.info("Closing %s" % self) DataFile.opened_queue[0].close() del DataFile.opened_queue[0] DataFile.opened_queue.append(self) log.info("Opening %s" % self) return self._record_index.get_handles() def process_var_binds(self, var_binds, **context): rsp_var_binds = [] if context.get("nextFlag"): error_status = exval.endOfMib else: error_status = exval.noSuchInstance try: text, db = self.get_handles() except SnmpsimError as exc: log.error("Problem with data file or its index: %s" % exc) ReportingManager.update_metrics( data_file=self._text_file, datafile_failure_count=1, transport_call_count=1, **context, ) return [(vb[0], error_status) for vb in var_binds] vars_remaining = vars_total = len(var_binds) err_total = 0 log.info( "Request var-binds: %s, flags: %s, " "%s" % ( ", ".join([f"{vb[0]}=<{vb[1].prettyPrint()}>" for vb in var_binds]), context.get("nextFlag") and "NEXT" or "EXACT", context.get("setFlag") and "SET" or "GET", ) ) separator = b"," for oid, val in var_binds: text_oid = str(univ.OctetString(".".join(["%s" % x for x in oid]))) try: line = self._record_index.lookup( str(univ.OctetString(".".join(["%s" % x for x in oid]))) ) except KeyError: offset = search_record_by_oid(oid, text, self._text_parser) subtree_flag = exact_match = False else: offset, subtree_flag, prev_offset = line.split(separator, 2) subtree_flag, exact_match = int(subtree_flag), True offset = int(offset) text.seek(offset) vars_remaining -= 1 line, _, _ = get_record(text) # matched line while True: if exact_match: if context.get("nextFlag") and not subtree_flag: _next_line, _, _ = get_record(text) # next line if _next_line: _next_oid, _ = self._text_parser.evaluate( _next_line, oidOnly=True ) try: _, subtree_flag, _ = self._record_index.lookup( str(_next_oid) ).split(separator, 2) except KeyError: log.error( "data error for %s at %s, index " "broken?" % (self, _next_oid) ) line = "" # fatal error else: subtree_flag = int(subtree_flag) line = _next_line else: line = _next_line else: # search function above always rounds up to the next OID if line: _oid, _ = self._text_parser.evaluate(line, oidOnly=True) else: # eom _oid = "last" try: _, _, _prev_offset = self._record_index.lookup(str(_oid)).split( separator, 2 ) except KeyError: log.error( "data error for %s at %s, index " "broken?" % (self, _oid) ) line = "" # fatal error else: _prev_offset = int(_prev_offset) # previous line serves a subtree? if _prev_offset >= 0: text.seek(_prev_offset) _prev_line, _, _ = get_record(text) _prev_oid, _ = self._text_parser.evaluate( _prev_line, oidOnly=True ) if _prev_oid.isPrefixOf(oid): # use previous line to the matched one line = _prev_line subtree_flag = True if not line: _oid = oid _val = error_status break call_context = context.copy() call_context.update( (), origOid=oid, origValue=val, dataFile=self._text_file, subtreeFlag=subtree_flag, exactMatch=exact_match, errorStatus=error_status, varsTotal=vars_total, varsRemaining=vars_remaining, variationModules=self._variation_modules, ) try: _oid, _val = self._text_parser.evaluate(line, **call_context) if _val is exval.endOfMib: exact_match = True subtree_flag = False continue except NoDataNotification: raise except MibOperationError: raise except Exception as exc: _oid = oid _val = error_status err_total += 1 log.error(f"data error at {self} for {text_oid}: {exc}") break rsp_var_binds.append((_oid, _val)) log.info( "Response var-binds: %s" % (", ".join([f"{vb[0]}=<{vb[1].prettyPrint()}>" for vb in rsp_var_binds])) ) ReportingManager.update_metrics( data_file=self._text_file, varbind_count=vars_total, datafile_call_count=1, datafile_failure_count=err_total, transport_call_count=1, **context, ) return rsp_var_binds def __str__(self): return "%s controller" % self._text_file def get_data_files(tgt_dir, top_len=None): # If top_len is not provided, calculate it based on the target directory if top_len is None: top_len = len(tgt_dir.rstrip(os.path.sep).split(os.path.sep)) # Start processing the directory return process_directory(tgt_dir, top_len) def process_directory(tgt_dir, top_len): # Initialize an empty list to store directory content dir_content = [] # Iterate over each file in the target directory for d_file in os.listdir(tgt_dir): # Get the full path of the file full_path = os.path.join(tgt_dir, d_file) # Get the inode information of the file inode = os.lstat(full_path) # If the file is a symbolic link, process it if stat.S_ISLNK(inode.st_mode): full_path, inode = process_symlink(full_path, tgt_dir) # Calculate the relative path of the file rel_path = full_path.split(os.path.sep)[top_len:] # If the file is a directory, recursively process it if stat.S_ISDIR(inode.st_mode): dir_content += get_data_files(full_path, top_len) # If the file is a regular file, process it elif stat.S_ISREG(inode.st_mode): dir_content += process_file(d_file, full_path, rel_path) # Return the directory content return dir_content def process_symlink(full_path, tgt_dir): # Read the target of the symbolic link full_path = os.readlink(full_path) # If the target is not an absolute path, prepend the target directory if not os.path.isabs(full_path): full_path = os.path.join(tgt_dir, full_path) # Get the inode information of the target file inode = os.stat(full_path) # Return the full path and inode return full_path, inode def process_file(d_file, full_path, rel_path): # Check if the file extension matches any of the record types for dExt in variation.RECORD_TYPES: if d_file.endswith(dExt): # If it does, process the file extension return process_file_extension(d_file, full_path, rel_path, dExt) # If it does not, return an empty list return [] def process_file_extension(d_file, full_path, rel_path, dExt): # Process the relative path to create an identifier for the file if rel_path[0] == SELF_LABEL: rel_path = rel_path[1:] if len(rel_path) == 1 and rel_path[0] == SELF_LABEL + os.path.extsep + dExt: rel_path[0] = rel_path[0][4:] # Join the elements of the relative path to create the identifier ident = os.path.join(*rel_path) # Remove the file extension from the identifier ident = ident[: -len(dExt) - 1] # Replace any path separators in the identifier with a forward slash ident = ident.replace(os.path.sep, "/") # Return a tuple containing the full path, the record type, and the identifier return [(full_path, variation.RECORD_TYPES[dExt], ident)] def probe_context(transport_domain, transport_address, context_engine_id, context_name): """Suggest variations of context name based on request data""" if context_engine_id: candidate = [ context_engine_id, context_name, ".".join([str(x) for x in transport_domain]), ] else: # try legacy layout w/o contextEngineId in the path candidate = [context_name, ".".join([str(x) for x in transport_domain])] if transport_domain[: len(udp.domainName)] == udp.domainName: candidate.append(transport_address[0]) elif udp6 and transport_domain[: len(udp6.domainName)] == udp6.domainName: candidate.append(str(transport_address[0]).replace(":", "_")) candidate = [str(x) for x in candidate if x] while candidate: yield rfc1902.OctetString( os.path.normpath(os.path.sep.join(candidate)).replace(os.path.sep, "/") ).asOctets() del candidate[-1] # try legacy layout w/o contextEngineId in the path if context_engine_id: for candidate in probe_context( transport_domain, transport_address, None, context_name ): yield candidate snmpsim-1.2.2/snmpsim/endpoints.py0000644000000000000000000000472613615410400014176 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP transport endpoints initialization harness # import socket from pysnmp.carrier.asyncio.dgram import udp from pysnmp.carrier.asyncio.dgram import udp6 from snmpsim.error import SnmpsimError class TransportEndpointsBase: def __init__(self): self.__endpoint = None def add(self, addr): self.__endpoint = self._addEndpoint(addr) return self def _addEndpoint(self, addr): raise NotImplementedError() def __len__(self): return len(self.__endpoint) def __getitem__(self, i): return self.__endpoint[i] class IPv4TransportEndpoints(TransportEndpointsBase): def _addEndpoint(self, addr): f = lambda h, p=161: (h, int(p)) try: h, p = f(*addr.split(":")) except Exception: raise SnmpsimError("improper IPv4/UDP endpoint %s" % addr) return udp.UdpTransport().open_server_mode((h, p)), addr class IPv6TransportEndpoints(TransportEndpointsBase): def _addEndpoint(self, addr): if not udp6: raise SnmpsimError("This system does not support UDP/IP6") if addr.find("]:") != -1 and addr[0] == "[": h, p = addr.split("]:") try: h, p = h[1:], int(p) except Exception: raise SnmpsimError("improper IPv6/UDP endpoint %s" % addr) elif addr[0] == "[" and addr[-1] == "]": h, p = addr[1:-1], 161 else: h, p = addr, 161 return udp6.Udp6Transport().openServerMode((h, p)), addr def parse_endpoint(arg, ipv6=False): address = arg # IPv6 notation if ipv6 and address.startswith("["): address = address.replace("[", "").replace("]", "") try: if ":" in address: address, port = address.rsplit(":", 1) port = int(port) else: port = 161 except Exception as exc: raise SnmpsimError(f"Malformed network endpoint address {arg}: {exc}") try: address, port = socket.getaddrinfo( address, port, socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, )[0][4][:2] except socket.gaierror as exc: raise SnmpsimError(f"Unknown hostname {address}: {exc}") return address, port snmpsim-1.2.2/snmpsim/error.py0000644000000000000000000000115513615410400013315 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # class SnmpsimError(Exception): pass class NoDataNotification(SnmpsimError): pass class MoreDataNotification(SnmpsimError): def __init__(self, **kwargs): self.__kwargs = kwargs def __contains__(self, key): return key in self.__kwargs def __getitem__(self, key): return self.__kwargs[key] def get(self, key): return self.__kwargs.get(key) def keys(self): return self.__kwargs.keys() snmpsim-1.2.2/snmpsim/log.py0000644000000000000000000002143513615410400012750 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # import logging import os import socket import sys import time from logging import handlers from snmpsim.error import SnmpsimError LOG_DEBUG = 0 LOG_INFO = 1 LOG_ERROR = 2 class AbstractLogger: def __init__(self, progId, *priv): self._logger = logging.getLogger(progId) self._logger.setLevel(logging.DEBUG) self._progId = progId self._ident = 0 self.init(*priv) def __call__(self, s): self._logger.debug(" " * self._ident + s) def inc_ident(self, amount=2): self._ident += amount def dec_ident(self, amount=2): self._ident -= amount if self._ident < 0: self._ident = 0 def init(self, *priv): pass class SyslogLogger(AbstractLogger): SYSLOG_SOCKET_PATHS = ("/dev/log", "/var/run/syslog") def init(self, *priv): if len(priv) < 1: raise SnmpsimError( "Bad syslog params, need at least facility, also accept " "host, port, socktype (tcp|udp)" ) if len(priv) < 2: priv = [priv[0], "debug"] if len(priv) < 3: # search for syslog local socket for dev in self.SYSLOG_SOCKET_PATHS: if os.path.exists(dev): priv = [priv[0], priv[1], dev] break else: priv = [priv[0], priv[1], "localhost", 514, "udp"] if not priv[2].startswith("/"): if len(priv) < 4: priv = [priv[0], priv[1], priv[2], 514, "udp"] if len(priv) < 5: priv = [priv[0], priv[1], priv[2], 514, "udp"] priv = [priv[0], priv[1], priv[2], int(priv[3]), priv[4]] try: handler = handlers.SysLogHandler( address=( priv[2].startswith("/") and priv[2] or (priv[2], int(priv[3])) ), facility=priv[0].lower(), socktype=( len(priv) > 4 and priv[4] == "tcp" and socket.SOCK_STREAM or socket.SOCK_DGRAM ), ) except Exception as exc: raise SnmpsimError("Bad syslog option(s): %s" % exc) handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s")) self._logger.addHandler(handler) class FileLogger(AbstractLogger): class TimedRotatingFileHandler(handlers.TimedRotatingFileHandler): """Store log creation time in a stand-alone file''s mtime""" def __init__(self, *args, **kwargs): handlers.TimedRotatingFileHandler.__init__(self, *args, **kwargs) self._failure = False if not os.path.exists(self._filename): return timestamp = os.stat(self._filename).st_mtime # Use a stand-aside file metadata time instead of the last # modification of the log file itself, as the stock # implementation does. # This is to work-around the increasing rotation intervals # on process restart. self.rollover_at = self.computeRollover(timestamp) @property def _filename(self): return os.path.join( os.path.dirname(self.baseFilename), "." + os.path.basename(self.baseFilename) + "-timestamp", ) def doRollover(self): try: handlers.TimedRotatingFileHandler.doRollover(self) # note log file creation time if os.path.exists(self._filename): os.unlink(self._filename) open(self._filename, "w").close() self._failure = False except OSError as exc: # File rotation seems to fail, postpone the next run timestamp = time.time() self.rollover_at = self.computeRollover(timestamp) if not self._failure: self._failure = True error( "Failed to rotate log/timestamp file " "%s: %s" % (self._filename, exc) ) def init(self, *priv): if not priv: raise SnmpsimError("Bad log file params, need filename") if sys.platform[:3] == "win": # fix possibly corrupted absolute windows path if len(priv[0]) == 1 and priv[0].isalpha() and len(priv) > 1: priv = [priv[0] + ":" + priv[1]] + list(priv[2:]) maxsize = 0 maxage = None if len(priv) > 1 and priv[1]: try: if priv[1][-1] == "k": maxsize = int(priv[1][:-1]) * 1024 elif priv[1][-1] == "m": maxsize = int(priv[1][:-1]) * 1024 * 1024 elif priv[1][-1] == "g": maxsize = int(priv[1][:-1]) * 1024 * 1024 * 1024 elif priv[1][-1] == "S": maxage = ("S", int(priv[1][:-1])) elif priv[1][-1] == "M": maxage = ("M", int(priv[1][:-1])) elif priv[1][-1] == "H": maxage = ("H", int(priv[1][:-1])) elif priv[1][-1] == "D": maxage = ("D", int(priv[1][:-1])) else: raise ValueError("Unknown log rotation criterion: %s" % priv[1][-1]) except ValueError: raise SnmpsimError( "Error in timed log rotation specification. Use " "k,m,g for size or S,M,H,D for time limits" ) try: if maxsize: handler = handlers.RotatingFileHandler( priv[0], backupCount=30, maxBytes=maxsize ) elif maxage: handler = self.TimedRotatingFileHandler( priv[0], backupCount=30, when=maxage[0], interval=maxage[1] ) else: handler = handlers.WatchedFileHandler(priv[0]) except Exception as exc: raise SnmpsimError("Failure configure logging: %s" % exc) handler.setFormatter(logging.Formatter("%(message)s")) self._logger.addHandler(handler) self( "Log file %s, rotation rules: " "%s" % ( priv[0], maxsize and "> %sKB" % (maxsize / 1024) or maxage and f"{maxage[1]}{maxage[0]}" or "", ) ) def __call__(self, s): now = time.time() timestamp = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(now)) AbstractLogger.__call__( self, "%s.%02d %s: %s" % (timestamp, now % 1 * 100, self._progId, s) ) class StreamLogger(AbstractLogger): stream = sys.stderr def init(self, *priv): try: handler = logging.StreamHandler(self.stream) except AttributeError as exc: raise SnmpsimError("Stream logger failure: %s" % exc) handler.setFormatter(logging.Formatter("%(message)s")) self._logger.addHandler(handler) class StdoutLogger(StreamLogger): stream = sys.stdout class StderrLogger(StreamLogger): stream = sys.stderr class NullLogger(AbstractLogger): def init(self, *priv): handler = logging.NullHandler() self._logger.addHandler(handler) def __call__(self, s): pass METHODS_MAP = { "syslog": SyslogLogger, "file": FileLogger, "stdout": StdoutLogger, "stderr": StderrLogger, "null": NullLogger, } LEVELS_MAP = { "debug": LOG_DEBUG, "info": LOG_INFO, "error": LOG_ERROR, } msg = lambda x: None log_level = LOG_INFO def error(message, ctx=""): if log_level <= LOG_ERROR: msg(f"ERROR {message} {ctx}") def info(message, ctx=""): if log_level <= LOG_INFO: msg(f"{message} {ctx}") def debug(message, ctx=""): if log_level <= LOG_DEBUG: msg(f"DEBUG {message} {ctx}") def set_level(level): global log_level try: log_level = LEVELS_MAP[level] except KeyError: raise SnmpsimError( 'Unknown log level "%s", known levels are: ' "%s" % (level, ", ".join(LEVELS_MAP)) ) def set_logger(progId, *priv, **options): global msg try: if not isinstance(msg, AbstractLogger) or options.get("force"): msg = METHODS_MAP[priv[0]](progId, *priv[1:]) except KeyError: raise SnmpsimError( 'Unknown logging method "%s", known methods are: ' "%s" % (priv[0], ", ".join(METHODS_MAP)) ) snmpsim-1.2.2/snmpsim/utils.py0000644000000000000000000000273213615410400013326 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # import asyncio import importlib import sys import threading import pysnmp import pysmi import pyasn1 import snmpsim TITLE = """\ SNMP Simulator version {}, written by Ilya Etingof Using foundation libraries: pysmi {}, pysnmp {}, pyasn1 {}. Python interpreter: {} Documentation and support at https://www.pysnmp.com/snmpsim """.format( snmpsim.__version__, pysmi.__version__, pysnmp.__version__, pyasn1.__version__, sys.version, ) def try_load(module, package=None): """Try to load given module, return `None` on failure""" try: return importlib.import_module(module, package) except ImportError: return def split(val, sep): """Split a string into a list based on a separator""" for x in (3, 2, 1): if val.find(sep * x) != -1: return val.split(sep * x) return [val] def run_in_new_loop(coroutine): """Run a coroutine in a new event loop and return its result""" result = None def run(): nonlocal result new_loop = asyncio.new_event_loop() asyncio.set_event_loop(new_loop) try: result = new_loop.run_until_complete(coroutine) finally: new_loop.close() thread = threading.Thread(target=run) thread.start() thread.join() return result snmpsim-1.2.2/snmpsim/variation.py0000644000000000000000000002276513615410400014172 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # Variation module support in simulation data # import os from pyasn1.error import PyAsn1Error from pyasn1.type import univ from pysnmp.smi.error import MibOperationError from snmpsim import log from snmpsim.error import NoDataNotification from snmpsim.error import SnmpsimError from snmpsim.record import dump from snmpsim.record import mvc from snmpsim.record import sap from snmpsim.record import snmprec from snmpsim.record import walk from snmpsim.reporting.manager import ReportingManager RECORD_TYPES = { dump.DumpRecord.ext: dump.DumpRecord(), mvc.MvcRecord.ext: mvc.MvcRecord(), sap.SapRecord.ext: sap.SapRecord(), walk.WalkRecord.ext: walk.WalkRecord(), } class SnmprecRecordMixIn: def evaluate_value(self, oid, tag, value, **context): # Variation module reference if ":" in tag: mod_name, tag = tag[tag.index(":") + 1 :], tag[: tag.index(":")] else: mod_name = None if mod_name: if ( "variationModules" in context and mod_name in context["variationModules"] ): if "dataValidation" in context: return oid, tag, univ.Null else: if context["setFlag"]: hexvalue = self.grammar.hexify_value(context["origValue"]) if hexvalue is not None: context["hexvalue"] = hexvalue context["hextag"] = self.grammar.get_tag_by_type( context["origValue"] ) context["hextag"] += "x" # prepare agent and record contexts on first reference (variation_module, agent_contexts, record_contexts) = context[ "variationModules" ][mod_name] if context["dataFile"] not in agent_contexts: agent_contexts[context["dataFile"]] = {} if context["dataFile"] not in record_contexts: record_contexts[context["dataFile"]] = {} variation_module["agentContext"] = agent_contexts[ context["dataFile"] ] record_contexts = record_contexts[context["dataFile"]] if oid not in record_contexts: record_contexts[oid] = {} variation_module["recordContext"] = record_contexts[oid] handler = variation_module["variate"] # invoke variation module oid, tag, value = handler(oid, tag, value, **context) ReportingManager.update_metrics( variation=mod_name, variation_call_count=1, **context ) else: ReportingManager.update_metrics( variation=mod_name, variation_failure_count=1, **context ) raise SnmpsimError( 'Variation module "%s" referenced but not ' "loaded\r\n" % mod_name ) if not mod_name: if "dataValidation" in context: snmprec.SnmprecRecord.evaluate_value(self, oid, tag, value, **context) if ( not context["nextFlag"] and not context["exactMatch"] or context["setFlag"] ): return context["origOid"], tag, context["errorStatus"] if not hasattr(value, "tagSet"): # not already a pyasn1 object return snmprec.SnmprecRecord.evaluate_value( self, oid, tag, value, **context ) return oid, tag, value def evaluate(self, line, **context): oid, tag, value = self.grammar.parse(line) oid = self.evaluate_oid(oid) if context.get("oidOnly"): value = None else: try: oid, tag, value = self.evaluate_value(oid, tag, value, **context) except NoDataNotification: raise except MibOperationError: raise except PyAsn1Error as exc: raise SnmpsimError( "value evaluation for %s = %r failed: " "%s\r\n" % (oid, value, exc) ) return oid, value def format_value(self, oid, value, **context): (text_oid, text_tag, text_value) = snmprec.SnmprecRecord.format_value( self, oid, value ) # invoke variation module if context["variationModule"]: (plain_oid, plain_tag, plain_value) = snmprec.SnmprecRecord.format_value( self, oid, value, nohex=True ) if plain_tag != text_tag: context["hextag"], context["hexvalue"] = text_tag, text_value else: text_tag, text_value = plain_tag, plain_value handler = context["variationModule"]["record"] text_oid, text_tag, text_value = handler( text_oid, text_tag, text_value, **context ) elif "stopFlag" in context and context["stopFlag"]: raise NoDataNotification() return text_oid, text_tag, text_value class SnmprecRecord(SnmprecRecordMixIn, snmprec.SnmprecRecord): pass RECORD_TYPES[SnmprecRecord.ext] = SnmprecRecord() class CompressedSnmprecRecord(SnmprecRecordMixIn, snmprec.CompressedSnmprecRecord): pass RECORD_TYPES[CompressedSnmprecRecord.ext] = CompressedSnmprecRecord() def load_variation_modules(search_path, modules_options): variation_modules = {} modules_options = modules_options.copy() for variation_modules_dir in search_path: log.info( 'Scanning "%s" directory for variation ' "modules..." % variation_modules_dir ) if not os.path.exists(variation_modules_dir): log.info('Directory "%s" does not exist' % variation_modules_dir) continue for d_file in os.listdir(variation_modules_dir): if d_file[-3:] != ".py": continue _to_load = [] mod_name = os.path.splitext(os.path.basename(d_file))[0] if mod_name in modules_options: while modules_options[mod_name]: alias, params = modules_options[mod_name].pop() _to_load.append((alias, params)) del modules_options[mod_name] else: _to_load.append((mod_name, "")) mod = os.path.abspath(os.path.join(variation_modules_dir, d_file)) for alias, params in _to_load: if alias in variation_modules: log.error( 'ignoring duplicate variation module "%s" at ' '"%s"' % (alias, mod) ) continue ctx = {"path": mod, "alias": alias, "args": params, "moduleContext": {}} try: with open(mod) as fl: exec(compile(fl.read(), mod, "exec"), ctx) except Exception as exc: log.error( 'Variation module "%s" execution failure: ' "%s" % (mod, exc) ) return 1 # moduleContext, agentContexts, recordContexts variation_modules[alias] = ctx, {}, {} log.info( "A total of %s modules found in " "%s" % (len(variation_modules), variation_modules_dir) ) if modules_options: log.info( "WARNING: unused options for variation modules: " "%s" % ", ".join(modules_options) ) return variation_modules def initialize_variation_modules(variation_modules, mode): log.info("Initializing variation modules...") for name, modules_contexts in variation_modules.items(): body = modules_contexts[0] for handler in ("init", "variate", "shutdown"): if handler not in body: log.error( 'missing "%s" handler at variation module ' '"%s"' % (handler, name) ) return 1 try: body["init"](options=body["args"], mode=mode) except Exception as exc: log.error( 'Variation module "%s" from "%s" load FAILED: ' "%s" % (body["alias"], body["path"], exc) ) else: log.info( 'Variation module "%s" from "%s" ' "loaded OK" % (body["alias"], body["path"]) ) def parse_modules_options(options): variation_modules_options = {} for option in options: args = option.split(":", 1) try: mod_name, args = args[0], args[1] except Exception as exc: raise SnmpsimError( "ERROR: improper variation module options %s: %s\r\n", exc ) if "=" in mod_name: mod_name, alias = mod_name.split("=", 1) else: alias = os.path.splitext(os.path.basename(mod_name))[0] if mod_name not in variation_modules_options: variation_modules_options[mod_name] = [] variation_modules_options[mod_name].append((alias, args)) return variation_modules_options snmpsim-1.2.2/snmpsim/commands/__init__.py0000644000000000000000000000000013615410400015510 0ustar00snmpsim-1.2.2/snmpsim/commands/cmd2rec.py0000644000000000000000000006343013615410400015310 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP Snapshot Data Recorder # import argparse import functools import os import sys import time import traceback from pyasn1 import debug as pyasn1_debug from pyasn1.type import univ from pysnmp import debug as pysnmp_debug from pysnmp.carrier.asyncio.dgram import udp from pysnmp.carrier.asyncio.dgram import udp6 from pysnmp.entity import engine, config from pysnmp.entity.rfc3413 import cmdgen from pysnmp.error import PySnmpError from pysnmp.proto import rfc1902 from pysnmp.proto import rfc1905 from pysnmp.smi import compiler from pysnmp.smi import view from pysnmp.smi.rfc1902 import ObjectIdentity from snmpsim import confdir from snmpsim import error from snmpsim import log from snmpsim import utils from snmpsim import variation from snmpsim import endpoints AUTH_PROTOCOLS = { "MD5": config.usmHMACMD5AuthProtocol, "SHA": config.usmHMACSHAAuthProtocol, "SHA224": config.usmHMAC128SHA224AuthProtocol, "SHA256": config.usmHMAC192SHA256AuthProtocol, "SHA384": config.usmHMAC256SHA384AuthProtocol, "SHA512": config.usmHMAC384SHA512AuthProtocol, "NONE": config.usmNoAuthProtocol, } PRIV_PROTOCOLS = { "DES": config.usmDESPrivProtocol, "3DES": config.usm3DESEDEPrivProtocol, "AES": config.usmAesCfb128Protocol, "AES128": config.usmAesCfb128Protocol, "AES192": config.usmAesCfb192Protocol, "AES192BLMT": config.usmAesBlumenthalCfb192Protocol, "AES256": config.usmAesCfb256Protocol, "AES256BLMT": config.usmAesBlumenthalCfb256Protocol, "NONE": config.usmNoPrivProtocol, } VERSION_MAP = {"1": 0, "2c": 1, "3": 3} DESCRIPTION = "SNMP simulation data recorder. Pull simulation data from SNMP agent" def _parse_mib_object(arg, last=False): if "::" in arg: return ObjectIdentity(*arg.split("::", 1), last=last) else: return univ.ObjectIdentifier(arg) def _parse_sized_string(arg, min_length=8): if len(arg) < min_length: raise argparse.ArgumentTypeError( f'Value "{arg}" must be {min_length}+ chars of length' ) return arg def main(): variation_module = None parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("-v", "--version", action="version", version=utils.TITLE) parser.add_argument( "--quiet", action="store_true", help="Do not print out informational messages" ) parser.add_argument( "--debug", choices=pysnmp_debug.flagMap, action="append", type=str, default=[], help="Enable one or more categories of SNMP debugging.", ) parser.add_argument( "--debug-asn1", choices=pyasn1_debug.FLAG_MAP, action="append", type=str, default=[], help="Enable one or more categories of ASN.1 debugging.", ) parser.add_argument( "--logging-method", type=lambda x: x.split(":"), metavar="=<%s[:args]>]" % "|".join(log.METHODS_MAP), default="stderr", help="Logging method.", ) parser.add_argument( "--log-level", choices=log.LEVELS_MAP, type=str, default="info", help="Logging level.", ) v1arch_group = parser.add_argument_group("SNMPv1/v2c parameters") v1arch_group.add_argument( "--protocol-version", choices=["1", "2c"], default="2c", help="SNMPv1/v2c protocol version", ) v1arch_group.add_argument( "--community", type=str, default="public", help="SNMP community name" ) v3arch_group = parser.add_argument_group("SNMPv3 parameters") v3arch_group.add_argument( "--v3-user", metavar="", type=functools.partial(_parse_sized_string, min_length=1), help="SNMPv3 USM user (security) name", ) v3arch_group.add_argument( "--v3-auth-key", type=_parse_sized_string, help="SNMPv3 USM authentication key (must be > 8 chars)", ) v3arch_group.add_argument( "--v3-auth-proto", choices=AUTH_PROTOCOLS, type=lambda x: x.upper(), default="NONE", help="SNMPv3 USM authentication protocol", ) v3arch_group.add_argument( "--v3-priv-key", type=_parse_sized_string, help="SNMPv3 USM privacy (encryption) key (must be > 8 chars)", ) v3arch_group.add_argument( "--v3-priv-proto", choices=PRIV_PROTOCOLS, type=lambda x: x.upper(), default="NONE", help="SNMPv3 USM privacy (encryption) protocol", ) v3arch_group.add_argument( "--v3-context-engine-id", type=lambda x: univ.OctetString(hexValue=x[2:]), help="SNMPv3 context engine ID", ) v3arch_group.add_argument( "--v3-context-name", type=str, default="", help="SNMPv3 context engine ID" ) parser.add_argument( "--use-getbulk", action="store_true", help="Use SNMP GETBULK PDU for mass SNMP managed objects retrieval", ) parser.add_argument( "--getbulk-repetitions", type=int, default=25, help="Use SNMP GETBULK PDU for mass SNMP managed objects retrieval", ) endpoint_group = parser.add_mutually_exclusive_group(required=True) endpoint_group.add_argument( "--agent-udpv4-endpoint", type=endpoints.parse_endpoint, metavar="<[X.X.X.X]:NNNNN>", help="SNMP agent UDP/IPv4 address to pull simulation data from (name:port)", ) endpoint_group.add_argument( "--agent-udpv6-endpoint", type=functools.partial(endpoints.parse_endpoint, ipv6=True), metavar="<[X:X:..X]:NNNNN>", help="SNMP agent UDP/IPv6 address to pull simulation data from ([name]:port)", ) parser.add_argument( "--timeout", type=int, default=3, help="SNMP command response timeout (in seconds)", ) parser.add_argument("--retries", type=int, default=3, help="SNMP command retries") parser.add_argument( "--start-object", metavar="", type=_parse_mib_object, default=univ.ObjectIdentifier("1.3.6"), help="Drop all simulation data records prior to this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--stop-object", metavar="", type=functools.partial(_parse_mib_object, last=True), help="Drop all simulation data records after this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--mib-source", dest="mib_sources", metavar="", action="append", type=str, default=["https://mibs.pysnmp.com/asn1/@mib@"], help="One or more URIs pointing to a collection of ASN.1 MIB files." 'Optional "@mib@" token gets replaced with desired MIB module ' "name during MIB search.", ) parser.add_argument( "--destination-record-type", choices=variation.RECORD_TYPES, default="snmprec", help="Produce simulation data with record of this type", ) parser.add_argument( "--output-file", metavar="", type=str, help="SNMP simulation data file to write records to", ) parser.add_argument( "--continue-on-errors", metavar="", type=int, default=0, help="Keep on pulling SNMP data even if intermittent errors occur", ) variation_group = parser.add_argument_group("Simulation data variation options") parser.add_argument( "--variation-modules-dir", action="append", type=str, help="Search variation module by this path", ) variation_group.add_argument( "--variation-module", type=str, help="Pass gathered simulation data through this variation module", ) variation_group.add_argument( "--variation-module-options", type=str, default="", help="Variation module options", ) args = parser.parse_args() if args.debug: pysnmp_debug.setLogger(pysnmp_debug.Debug(*args.debug)) if args.debug_asn1: pyasn1_debug.setLogger(pyasn1_debug.Debug(*args.debug_asn1)) if args.output_file: ext = os.path.extsep ext += variation.RECORD_TYPES[args.destination_record_type].ext if not args.output_file.endswith(ext): args.output_file += ext record = variation.RECORD_TYPES[args.destination_record_type] args.output_file = record.open(args.output_file, "wb") else: args.output_file = sys.stdout if sys.version_info >= (3, 0, 0): # binary mode write args.output_file = sys.stdout.buffer elif sys.platform == "win32": import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) # Catch missing params if args.protocol_version == "3": if not args.v3_user: sys.stderr.write("ERROR: --v3-user is missing\r\n") parser.print_usage(sys.stderr) return 1 if args.v3_priv_key and not args.v3_auth_key: sys.stderr.write("ERROR: --v3-auth-key is missing\r\n") parser.print_usage(sys.stderr) return 1 if AUTH_PROTOCOLS[args.v3_auth_proto] == config.usmNoAuthProtocol: if args.v3_auth_key: args.v3_auth_proto = "MD5" else: if not args.v3_auth_key: sys.stderr.write("ERROR: --v3-auth-key is missing\r\n") parser.print_usage(sys.stderr) return 1 if PRIV_PROTOCOLS[args.v3_priv_proto] == config.usmNoPrivProtocol: if args.v3_priv_key: args.v3_priv_proto = "DES" else: if not args.v3_priv_key: sys.stderr.write("ERROR: --v3-priv-key is missing\r\n") parser.print_usage(sys.stderr) return 1 proc_name = os.path.basename(sys.argv[0]) try: log.set_logger(proc_name, *args.logging_method, force=True) if args.log_level: log.set_level(args.log_level) except error.SnmpsimError as exc: sys.stderr.write("%s\r\n" % exc) parser.print_usage(sys.stderr) return 1 if args.use_getbulk and args.protocol_version == "1": log.info("will be using GETNEXT with SNMPv1!") args.use_getbulk = False # Load variation module if args.variation_module: for variation_modules_dir in args.variation_modules_dir or confdir.variation: log.info( 'Scanning "%s" directory for variation ' "modules..." % variation_modules_dir ) if not os.path.exists(variation_modules_dir): log.info('Directory "%s" does not exist' % variation_modules_dir) continue mod = os.path.join(variation_modules_dir, args.variation_module + ".py") if not os.path.exists(mod): log.info('Variation module "%s" not found' % mod) continue ctx = {"path": mod, "moduleContext": {}} try: with open(mod) as fl: exec(compile(fl.read(), mod, "exec"), ctx) except Exception as exc: log.error('Variation module "%s" execution failure: %s' % (mod, exc)) return 1 variation_module = ctx log.info('Variation module "%s" loaded' % args.variation_module) break else: log.error('variation module "%s" not found' % args.variation_module) return 1 # SNMP configuration snmp_engine = engine.SnmpEngine() if args.protocol_version == "3": if args.v3_priv_key is None and args.v3_auth_key is None: secLevel = "noAuthNoPriv" elif args.v3_priv_key is None: secLevel = "authNoPriv" else: secLevel = "authPriv" config.addV3User( snmp_engine, args.v3_user, AUTH_PROTOCOLS[args.v3_auth_proto], args.v3_auth_key, PRIV_PROTOCOLS[args.v3_priv_proto], args.v3_priv_key, ) log.info( "SNMP version 3, Context EngineID: %s Context name: %s, SecurityName: %s, " "SecurityLevel: %s, Authentication key/protocol: %s/%s, Encryption " "(privacy) key/protocol: " "%s/%s" % ( args.v3_context_engine_id and args.v3_context_engine_id.prettyPrint() or "", args.v3_context_name and args.v3_context_name.prettyPrint() or "", args.v3_user, secLevel, args.v3_auth_key is None and "" or args.v3_auth_key, args.v3_auth_proto, args.v3_priv_key is None and "" or args.v3_priv_key, args.v3_priv_proto, ) ) else: args.v3_user = "agt" secLevel = "noAuthNoPriv" config.addV1System(snmp_engine, args.v3_user, args.community) log.info( "SNMP version %s, Community name: " "%s" % (args.protocol_version, args.community) ) config.addTargetParams( snmp_engine, "pms", args.v3_user, secLevel, VERSION_MAP[args.protocol_version] ) if args.agent_udpv6_endpoint: config.addSocketTransport( snmp_engine, udp6.domainName, udp6.Udp6SocketTransport().openClientMode() ) config.addTargetAddr( snmp_engine, "tgt", udp6.domainName, args.agent_udpv6_endpoint, "pms", args.timeout * 100, args.retries, ) log.info("Querying UDP/IPv6 agent at [%s]:%s" % args.agent_udpv6_endpoint) elif args.agent_udpv4_endpoint: config.addSocketTransport( snmp_engine, udp.domainName, udp.UdpAsyncioTransport().openClientMode() ) config.addTargetAddr( snmp_engine, "tgt", udp.domainName, args.agent_udpv4_endpoint, "pms", args.timeout * 100, args.retries, ) log.info("Querying UDP/IPv4 agent at %s:%s" % args.agent_udpv4_endpoint) log.info( "Agent response timeout: %d secs, retries: %s" % (args.timeout, args.retries) ) if isinstance(args.start_object, ObjectIdentity) or isinstance( args.stop_object, ObjectIdentity ): compiler.addMibCompiler(snmp_engine.getMibBuilder(), sources=args.mib_sources) mib_view_controller = view.MibViewController(snmp_engine.getMibBuilder()) try: if isinstance(args.start_object, ObjectIdentity): args.start_object.resolveWithMib(mib_view_controller) if isinstance(args.stop_object, ObjectIdentity): args.stop_object.resolveWithMib(mib_view_controller) except PySnmpError as exc: sys.stderr.write("ERROR: %s\r\n" % exc) return 1 # Variation module initialization if variation_module: log.info("Initializing variation module...") for x in ("init", "record", "shutdown"): if x not in variation_module: log.error( 'missing "%s" handler at variation module ' '"%s"' % (x, args.variation_module) ) return 1 try: handler = variation_module["init"] handler( snmpEngine=snmp_engine, options=args.variation_module_options, mode="recording", startOID=args.start_object, stopOID=args.stop_object, ) except Exception as exc: log.error( 'Variation module "%s" initialization FAILED: ' "%s" % (args.variation_module, exc) ) else: log.info('Variation module "%s" initialization OK' % args.variation_module) data_file_handler = variation.RECORD_TYPES[args.destination_record_type] # SNMP worker def cbFun( snmp_engine, send_request_handle, error_indication, error_status, error_index, var_bind_table, cb_ctx, ): if error_indication and not cb_ctx["retries"]: cb_ctx["errors"] += 1 log.error("SNMP Engine error: %s" % error_indication) return # SNMPv1 response may contain noSuchName error *and* SNMPv2c exception, # so we ignore noSuchName error here if error_status and error_status != 2 or error_indication: log.error( "Remote SNMP error %s" % (error_indication or error_status.prettyPrint()) ) if cb_ctx["retries"]: try: next_oid = var_bind_table[-1][0][0] except IndexError: next_oid = cb_ctx["lastOID"] else: log.error("Failed OID: %s" % next_oid) # fuzzy logic of walking a broken OID if len(next_oid) < 4: pass elif ( args.continue_on_errors - cb_ctx["retries"] ) * 10 / args.continue_on_errors > 5: next_oid = next_oid[:-2] + (next_oid[-2] + 1,) elif next_oid[-1]: next_oid = next_oid[:-1] + (next_oid[-1] + 1,) else: next_oid = next_oid[:-2] + (next_oid[-2] + 1, 0) cb_ctx["retries"] -= 1 cb_ctx["lastOID"] = next_oid log.info( "Retrying with OID %s (%s retries left)" "..." % (next_oid, cb_ctx["retries"]) ) # initiate another SNMP walk iteration if args.use_getbulk: cmd_gen.send_varbinds( snmp_engine, "tgt", args.v3_context_engine_id, args.v3_context_name, 0, args.getbulk_repetitions, [(next_oid, None)], cbFun, cb_ctx, ) else: cmd_gen.send_varbinds( snmp_engine, "tgt", args.v3_context_engine_id, args.v3_context_name, [(next_oid, None)], cbFun, cb_ctx, ) cb_ctx["errors"] += 1 return if args.continue_on_errors != cb_ctx["retries"]: cb_ctx["retries"] += 1 if var_bind_table and var_bind_table[-1] and var_bind_table[-1][0]: cb_ctx["lastOID"] = var_bind_table[-1][0][0] stop_flag = False # Walk var-binds for var_bind_row in var_bind_table: for oid, value in var_bind_row: # EOM if args.stop_object and oid >= args.stop_object: stop_flag = True # stop on out of range condition elif value is None or value.tagSet in ( rfc1905.NoSuchObject.tagSet, rfc1905.NoSuchInstance.tagSet, rfc1905.EndOfMibView.tagSet, ): stop_flag = True # remove value enumeration if value.tagSet == rfc1902.Integer32.tagSet: value = rfc1902.Integer32(value) if value.tagSet == rfc1902.Unsigned32.tagSet: value = rfc1902.Unsigned32(value) if value.tagSet == rfc1902.Bits.tagSet: value = rfc1902.OctetString(value) # Build .snmprec record context = { "origOid": oid, "origValue": value, "count": cb_ctx["count"], "total": cb_ctx["total"], "iteration": cb_ctx["iteration"], "reqTime": cb_ctx["reqTime"], "args.start_object": args.start_object, "stopOID": args.stop_object, "stopFlag": stop_flag, "variationModule": variation_module, } try: line = data_file_handler.format(oid, value, **context) except error.MoreDataNotification as exc: cb_ctx["count"] = 0 cb_ctx["iteration"] += 1 more_data_notification = exc if "period" in more_data_notification: log.info( "%s OIDs dumped, waiting %.2f sec(s)" "..." % (cb_ctx["total"], more_data_notification["period"]) ) time.sleep(more_data_notification["period"]) # initiate another SNMP walk iteration if args.use_getbulk: cmd_gen.send_varbinds( snmp_engine, "tgt", args.v3_context_engine_id, args.v3_context_name, 0, args.getbulk_repetitions, [(args.start_object, None)], cbFun, cb_ctx, ) else: cmd_gen.send_varbinds( snmp_engine, "tgt", args.v3_context_engine_id, args.v3_context_name, [(args.start_object, None)], cbFun, cb_ctx, ) stop_flag = True # stop current iteration except error.NoDataNotification: pass except error.SnmpsimError as exc: log.error(exc) continue else: args.output_file.write(line) cb_ctx["count"] += 1 cb_ctx["total"] += 1 if cb_ctx["count"] % 100 == 0: log.info( "OIDs dumped: %s/%s" % (cb_ctx["iteration"], cb_ctx["count"]) ) # Next request time cb_ctx["reqTime"] = time.time() # Continue walking return not stop_flag cb_ctx = { "total": 0, "count": 0, "errors": 0, "iteration": 0, "reqTime": time.time(), "retries": args.continue_on_errors, "lastOID": args.start_object, } if args.use_getbulk: cmd_gen = cmdgen.BulkCommandGenerator() cmd_gen.send_varbinds( snmp_engine, "tgt", args.v3_context_engine_id, args.v3_context_name, 0, args.getbulk_repetitions, [(args.start_object, rfc1902.Null(""))], cbFun, cb_ctx, ) else: cmd_gen = cmdgen.NextCommandGenerator() cmd_gen.send_varbinds( snmp_engine, "tgt", args.v3_context_engine_id, args.v3_context_name, [(args.start_object, rfc1902.Null(""))], cbFun, cb_ctx, ) log.info( "Sending initial %s request for %s (stop at %s)" "...." % ( args.use_getbulk and "GETBULK" or "GETNEXT", args.start_object, args.stop_object or "", ) ) started = time.time() try: snmp_engine.transportDispatcher.runDispatcher() except KeyboardInterrupt: log.info("Shutting down process...") finally: if variation_module: log.info("Shutting down variation module %s..." % args.variation_module) try: handler = variation_module["shutdown"] handler( snmpEngine=snmp_engine, options=args.variation_module_options, mode="recording", ) except Exception as exc: log.error( "Variation module %s shutdown FAILED: " "%s" % (args.variation_module, exc) ) else: log.info("Variation module %s shutdown OK" % args.variation_module) snmp_engine.transportDispatcher.closeDispatcher() started = time.time() - started cb_ctx["total"] += cb_ctx["count"] log.info( "OIDs dumped: %s, elapsed: %.2f sec, rate: %.2f OIDs/sec, errors: " "%d" % ( cb_ctx["total"], started, started and cb_ctx["count"] // started or 0, cb_ctx["errors"], ) ) args.output_file.flush() args.output_file.close() return cb_ctx.get("errors", 0) and 1 or 0 if __name__ == "__main__": try: rc = main() except KeyboardInterrupt: sys.stderr.write("shutting down process...") rc = 0 except Exception as exc: sys.stderr.write("process terminated: %s" % exc) for line in traceback.format_exception(*sys.exc_info()): sys.stderr.write(line.replace("\n", ";")) rc = 1 sys.exit(rc) snmpsim-1.2.2/snmpsim/commands/mib2rec.py0000644000000000000000000004516213615410400015316 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP Simulator MIB to data file converter # import argparse import functools import os import random import sys import traceback from pyasn1.error import PyAsn1Error from pyasn1.type import univ from pysnmp import debug from pysnmp.proto import rfc1902 from pysnmp.smi import builder from pysnmp.smi import compiler from pysnmp.smi import error from pysnmp.smi import view from pysnmp.smi.rfc1902 import ObjectIdentity from snmpsim import utils from snmpsim.error import SnmpsimError from snmpsim.record import dump from snmpsim.record import mvc from snmpsim.record import sap from snmpsim.record import snmprec from snmpsim.record import walk RECORD_TYPES = { dump.DumpRecord.ext: dump.DumpRecord(), mvc.MvcRecord.ext: mvc.MvcRecord(), sap.SapRecord.ext: sap.SapRecord(), walk.WalkRecord.ext: walk.WalkRecord(), snmprec.SnmprecRecord.ext: snmprec.SnmprecRecord(), snmprec.CompressedSnmprecRecord.ext: snmprec.CompressedSnmprecRecord(), } DESCRIPTION = ( "Converts MIB modules into SNMP simulation data files. " "Chooses random values or asks interactively. " "Fills SNMP conceptual tables with consistent indices." ) def _parse_mib_object(arg, last=False): if "::" in arg: return ObjectIdentity(*arg.split("::", 1), last=last) else: return univ.ObjectIdentifier(arg) def _parse_range(arg): try: minimum, maximum = (int(x) for x in arg.split(",")) except Exception as exc: raise SnmpsimError(f"Malformed integer range {arg}: {exc}") return minimum, maximum def main(): parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("-v", "--version", action="version", version=utils.TITLE) parser.add_argument( "--quiet", action="store_true", help="Do not print out informational messages" ) parser.add_argument( "--debug", choices=debug.flagMap, action="append", type=str, default=[], help="Enable one or more categories of SNMP debugging.", ) parser.add_argument( "--row-hint", dest="row_hint", action="store_true", help="Hint for MIBs Type" ) parser.add_argument( "--mib-source", dest="mib_sources", metavar="", action="append", type=str, default=["https://mibs.pysnmp.com/asn1/@mib@"], help="One or more URIs pointing to a collection of ASN.1 MIB files." 'Optional "@mib@" token gets replaced with desired MIB module ' "name during MIB search.", ) parser.add_argument( "--mib-module", dest="mib_modules", action="append", type=str, required=True, help="MIB module to generate simulation data from", ) parser.add_argument( "--start-object", metavar="", type=_parse_mib_object, help="Drop all simulation data records prior to this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--stop-object", metavar="", type=functools.partial(_parse_mib_object, last=True), help="Drop all simulation data records after this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--manual-values", action="store_true", help="Fill all managed objects values interactively", ) parser.add_argument( "--automatic-values", type=int, default=5000, help="Probe for suitable managed object value this many times " "prior to failing over to manual value specification", ) parser.add_argument( "--table-size", type=int, default=10, help="Generate SNMP conceptual tables with this many rows", ) parser.add_argument( "--destination-record-type", choices=RECORD_TYPES, default="snmprec", help="Produce simulation data with record of this type", ) parser.add_argument( "--output-file", metavar="", type=str, help="SNMP simulation data file to write records to", ) parser.add_argument( "--string-pool", metavar="", action="append", help="Words to use for simulated string values", ) parser.add_argument( "--string-pool-file", metavar="", type=str, help="File containing the words for simulating SNMP string values", ) parser.add_argument( "--integer32-range", metavar="", type=_parse_range, default=(0, 32), help="Range of values used to populate simulated Integer32 values", ) parser.add_argument( "--unsigned-range", metavar="", type=_parse_range, default=(0, 65535), help="Range of values used to populate simulated Unsigned values", ) parser.add_argument( "--counter-range", metavar="", type=_parse_range, default=(0, 0xFFFFFFFF), help="Range of values used to populate simulated Counter values", ) parser.add_argument( "--counter64-range", metavar="", type=_parse_range, default=(0, 0xFFFFFFFFFFFFFFFF), help="Range of values used to populate simulated Counter64 values", ) parser.add_argument( "--gauge-range", metavar="", type=_parse_range, default=(0, 0xFFFFFFFF), help="Range of values used to populate simulated Gauge values", ) parser.add_argument( "--timeticks-range", metavar="", type=_parse_range, default=(0, 0xFFFFFFFF), help="Range of values used to populate simulated Timeticks values", ) args = parser.parse_args() if args.debug: debug.setLogger(debug.Debug(*args.debug)) if args.manual_values: args.automatic_values = 0 if args.string_pool_file: with open(args.string_pool_file) as fl: args.string_pool = fl.read().split() elif not args.string_pool: args.string_pool = [ "Jaded", "zombies", "acted", "quaintly", "but", "kept", "driving", "their", "oxen", "forward", ] if args.output_file: ext = os.path.extsep + RECORD_TYPES[args.destination_record_type].ext if not args.output_file.endswith(ext): args.output_file += ext args.output_file = RECORD_TYPES[args.destination_record_type].open( args.output_file, "wb" ) else: args.output_file = sys.stdout if sys.version_info >= (3, 0, 0): # binary mode write args.output_file = sys.stdout.buffer elif sys.platform == "win32": import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) def get_value(syntax, hint="", automatic_values=args.automatic_values): make_guess = args.automatic_values val = None while True: if make_guess: if isinstance(syntax, rfc1902.IpAddress): val = ".".join([str(random.randrange(1, 256)) for x in range(4)]) elif isinstance(syntax, rfc1902.TimeTicks): val = random.randrange( args.timeticks_range[0], args.timeticks_range[1] ) elif isinstance(syntax, rfc1902.Gauge32): val = random.randrange(args.gauge_range[0], args.gauge_range[1]) elif isinstance(syntax, rfc1902.Counter32): val = random.randrange(args.counter_range[0], args.counter_range[1]) elif isinstance(syntax, rfc1902.Integer32): val = random.randrange( args.integer32_range[0], args.integer32_range[1] ) elif isinstance(syntax, rfc1902.Unsigned32): val = random.randrange( args.unsigned_range[0], args.unsigned_range[1] ) elif isinstance(syntax, rfc1902.Counter64): val = random.randrange( args.counter64_range[0], args.counter64_range[1] ) elif isinstance(syntax, univ.OctetString): maxWords = 10 val = " ".join( [ args.string_pool[random.randrange(0, len(args.string_pool))] for i in range(random.randrange(1, maxWords)) ] ) elif isinstance(syntax, univ.ObjectIdentifier): val = ".".join( ["1", "3", "6", "1", "3"] + [ "%d" % random.randrange(0, 255) for x in range(random.randrange(0, 10)) ] ) elif isinstance(syntax, rfc1902.Bits): val = [ random.randrange(0, 256) for x in range(random.randrange(0, 9)) ] else: val = "?" # remove value enumeration try: if syntax.tagSet == rfc1902.Integer32.tagSet: return rfc1902.Integer32(syntax.clone(val)) if syntax.tagSet == rfc1902.Unsigned32.tagSet: return rfc1902.Unsigned32(syntax.clone(val)) if syntax.tagSet == rfc1902.Bits.tagSet: return rfc1902.OctetString(syntax.clone(val)) return syntax.clone(val) except PyAsn1Error as exc: if make_guess == 1: sys.stderr.write( "*** Inconsistent value: %s\r\n*** See constraints and " "suggest a better one for:\r\n" % exc ) if make_guess: make_guess -= 1 continue sys.stderr.write( "%s# Value ['%s'] ? " % ( hint, (val is None and "" or val), ) ) sys.stderr.flush() line = sys.stdin.readline().strip() if line: if line[:2] == "0x": if line[:4] == "0x0x": line = line[2:] elif isinstance(syntax, univ.OctetString): val = syntax.clone(hexValue=line[2:]) else: val = int(line[2:], 16) else: val = line data_file_handler = snmprec.SnmprecRecord() mib_builder = builder.MibBuilder() # Load MIB tree foundation classes (MibScalar, MibTable, MibTableRow, MibTableColumn) = mib_builder.importSymbols( "SNMPv2-SMI", "MibScalar", "MibTable", "MibTableRow", "MibTableColumn" ) mib_view_controller = view.MibViewController(mib_builder) compiler.addMibCompiler(mib_builder, sources=args.mib_sources) try: if isinstance(args.start_object, ObjectIdentity): args.start_object.resolveWithMib(mib_view_controller) if isinstance(args.stop_object, ObjectIdentity): args.stop_object.resolveWithMib(mib_view_controller) except error.PySnmpError as exc: sys.stderr.write("ERROR: %s\r\n" % exc) return 1 output = [] # MIBs walk for modName in args.mib_modules: if not args.quiet: sys.stderr.write( "# MIB module: %s, from %s till " "%s\r\n" % ( modName, args.start_object or "the beginning", args.stop_object or "the end", ) ) try: oid = ObjectIdentity(modName).resolveWithMib(mib_view_controller) except error.PySnmpError as exc: sys.stderr.write("ERROR: failed on MIB %s: " "%s\r\n" % (modName, exc)) return 1 hint = row_hint = "" row_oid = None suffix = () this_table_size = 0 while True: try: oid, label, _ = mib_view_controller.getNextNodeName(oid) except error.NoSuchObjectError: break if row_oid and not row_oid.isPrefixOf(oid): this_table_size += 1 if args.automatic_values: if this_table_size < args.table_size: oid = tuple(row_oid) if not args.quiet: sys.stderr.write( "# Synthesizing row #%d of table %s\r\n" % (this_table_size, row_oid) ) else: if not args.quiet: sys.stderr.write( "# Finished table %s (%d rows)\r\n" % (row_oid, this_table_size) ) row_oid = None else: while True: sys.stderr.write( "# Synthesize row #%d for table %s (y/n)? " % (this_table_size, row_oid) ) sys.stderr.flush() line = sys.stdin.readline().strip() if line: if line[0] in ("y", "Y"): oid = tuple(row_oid) break elif line[0] in ("n", "N"): if not args.quiet: sys.stderr.write( "# Finished table %s (%d rows)\r\n" % (row_oid, this_table_size) ) row_oid = None break if args.start_object and oid < args.start_object: continue # skip on premature OID if args.stop_object and oid > args.stop_object: break # stop on out of range condition mib_name, sym_name, _ = mib_view_controller.getNodeLocation(oid) (node,) = mib_builder.importSymbols(mib_name, sym_name) if isinstance(node, MibTable): hint = f"# Table {mib_name}::{sym_name}\r\n" if not args.quiet: sys.stderr.write( "# Starting table %s::%s (%s)\r\n" % (mib_name, sym_name, univ.ObjectIdentifier(oid)) ) continue elif isinstance(node, MibTableRow): row_indices = {} suffix = () row_hint = hint + f"# Row {mib_name}::{sym_name}\r\n" for implied_flag, idx_mod_name, idx_sym_name in node.getIndexNames(): (idxNode,) = mib_builder.importSymbols(idx_mod_name, idx_sym_name) row_hint += "# Index {}::{} (type {})\r\n".format( idx_mod_name, idx_sym_name, idxNode.syntax.__class__.__name__, ) row_indices[idxNode.name] = get_value( idxNode.syntax, not args.quiet and row_hint or "" ) suffix = suffix + node.getAsName( row_indices[idxNode.name], implied_flag ) if not row_indices: if not args.quiet: sys.stderr.write( "# WARNING: %s::%s table has no index!\r\n" % (mib_name, sym_name) ) if row_oid is None: this_table_size = 0 row_oid = univ.ObjectIdentifier(oid) continue elif isinstance(node, MibTableColumn): oid = node.name if oid in row_indices: val = row_indices[oid] else: hint = "" if not args.quiet: hint += row_hint hint += "# Column %s::%s (type" " %s)\r\n" % ( mib_name, sym_name, node.syntax.__class__.__name__, ) val = get_value(node.syntax, hint) elif isinstance(node, MibScalar): hint = "" if not args.row_hint: hint += "# Scalar %s::%s (type %s)" "\r\n" % ( mib_name, sym_name, node.syntax.__class__.__name__, ) oid = node.name suffix = (0,) val = get_value(node.syntax, hint) else: hint = "" continue output.append((oid + suffix, val)) output.sort(key=lambda x: univ.ObjectIdentifier(x[0])) unique = set() for oid, val in output: if oid in unique: if not args.quiet: sys.stderr.write( "# Dropping duplicate OID %s\r\n" % (univ.ObjectIdentifier(oid),) ) else: try: args.output_file.write(data_file_handler.format(oid, val)) except SnmpsimError as exc: sys.stderr.write(f"ERROR: {exc}\r\n") else: unique.add(oid) if not args.quiet: sys.stderr.write(f"# End of {modName}, {len(unique)} OID(s) dumped\r\n") args.output_file.flush() args.output_file.close() return 0 if __name__ == "__main__": try: rc = main() except KeyboardInterrupt: sys.stderr.write("shutting down process...") rc = 0 except Exception as exc: sys.stderr.write("process terminated: %s" % exc) for line in traceback.format_exception(*sys.exc_info()): sys.stderr.write(line.replace("\n", ";")) rc = 1 sys.exit(rc) snmpsim-1.2.2/snmpsim/commands/pcap2rec.py0000644000000000000000000005443113615410400015471 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP Simulator MIB to data file converter # import argparse import bisect import functools import os import socket import struct import sys import time import traceback from pyasn1 import debug as pyasn1_debug from pyasn1.codec.ber import decoder from pyasn1.error import PyAsn1Error from pyasn1.type import univ from pysnmp import debug as pysnmp_debug from pysnmp.carrier.asyncio.dgram import udp from pysnmp.error import PySnmpError from pysnmp.proto import api from pysnmp.proto import rfc1905 from pysnmp.proto.rfc1902 import Bits from pysnmp.proto.rfc1902 import Integer32 from pysnmp.proto.rfc1902 import OctetString from pysnmp.proto.rfc1902 import Unsigned32 from pysnmp.smi import builder from pysnmp.smi import compiler from pysnmp.smi import rfc1902 from pysnmp.smi import view from snmpsim import confdir from snmpsim import error from snmpsim import log from snmpsim import utils from snmpsim.record import dump from snmpsim.record import mvc from snmpsim.record import sap from snmpsim.record import snmprec from snmpsim.record import walk pcap = utils.try_load("pcap") RECORD_TYPES = { dump.DumpRecord.ext: dump.DumpRecord(), mvc.MvcRecord.ext: mvc.MvcRecord(), sap.SapRecord.ext: sap.SapRecord(), walk.WalkRecord.ext: walk.WalkRecord(), snmprec.SnmprecRecord.ext: snmprec.SnmprecRecord(), snmprec.CompressedSnmprecRecord.ext: snmprec.CompressedSnmprecRecord(), } DESCRIPTION = ( "Snoops network traffic for SNMP responses, builds SNMP Simulator " "data files. Can read capture files or listen live network interface." ) class SnmprecRecord(snmprec.SnmprecRecord): def format_value(self, oid, value, **context): (text_oid, text_tag, text_value) = snmprec.SnmprecRecord.format_value( self, oid, value ) if context["variationModule"]: (plain_oid, plain_tag, plain_value) = snmprec.SnmprecRecord.format_value( self, oid, value, nohex=True ) if plain_tag != text_tag: context["hextag"], context["hexvalue"] = text_tag, text_value else: text_tag, text_value = plain_tag, plain_value handler = context["variationModule"]["record"] text_oid, text_tag, text_value = handler( text_oid, text_tag, text_value, **context ) elif "stopFlag" in context and context["stopFlag"]: raise error.NoDataNotification() return text_oid, text_tag, text_value def _parse_mib_object(arg, last=False): if "::" in arg: return rfc1902.ObjectIdentity(*arg.split("::", 1), last=last) else: return univ.ObjectIdentifier(arg) def main(): variation_module = None endpoints = {} contexts = {} stats = { "UDP packets": 0, "IP packets": 0, "bad packets": 0, "empty packets": 0, "unknown L2 protocol": 0, "SNMP errors": 0, "SNMP exceptions": 0, "agents seen": 0, "contexts seen": 0, "snapshots taken": 0, "Response PDUs seen": 0, "OIDs seen": 0, } parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("-v", "--version", action="version", version=utils.TITLE) parser.add_argument( "--quiet", action="store_true", help="Do not print out informational messages" ) parser.add_argument( "--debug", choices=pysnmp_debug.flagMap, action="append", type=str, default=[], help="Enable one or more categories of SNMP debugging.", ) parser.add_argument( "--debug-asn1", choices=pyasn1_debug.FLAG_MAP, action="append", type=str, default=[], help="Enable one or more categories of ASN.1 debugging.", ) parser.add_argument( "--logging-method", type=lambda x: x.split(":"), metavar="=<%s[:args]>]" % "|".join(log.METHODS_MAP), default="stderr", help="Logging method.", ) parser.add_argument( "--log-level", choices=log.LEVELS_MAP, type=str, default="info", help="Logging level.", ) parser.add_argument( "--start-object", metavar="", type=_parse_mib_object, default=univ.ObjectIdentifier("1.3.6"), help="Drop all simulation data records prior to this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--stop-object", metavar="", type=functools.partial(_parse_mib_object, last=True), help="Drop all simulation data records after this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--mib-source", dest="mib_sources", metavar="", action="append", type=str, default=["https://mibs.pysnmp.com/asn1/@mib@"], help="One or more URIs pointing to a collection of ASN.1 MIB files." 'Optional "@mib@" token gets replaced with desired MIB module ' "name during MIB search.", ) parser.add_argument( "--destination-record-type", choices=RECORD_TYPES, default="snmprec", help="Produce simulation data with record of this type", ) parser.add_argument( "--output-file", metavar="", type=str, help="SNMP simulation data file to write records to", ) variation_group = parser.add_argument_group("Simulation data variation options") variation_group.add_argument( "--variation-modules-dir", action="append", type=str, help="Search variation module by this path", ) variation_group.add_argument( "--variation-module", type=str, help="Pass gathered simulation data through this variation module", ) variation_group.add_argument( "--variation-module-options", type=str, default="", help="Variation module options", ) parser.add_argument( "--output-dir", metavar="", type=str, default=".", help="SNMP simulation data directory to place captured traffic in " "form of simulation records. File names reflect traffic sources " "on the network.", ) variation_group.add_argument( "--transport-id-offset", type=int, default=0, help="When arranging simulation data files, start enumerating " "receiving transport endpoints from this number.", ) traffic_group = parser.add_argument_group("Traffic capturing options") traffic_group.add_argument( "--packet-filter", type=str, default="udp and src port 161", help="Traffic filter (in tcpdump syntax) to use for picking SNMP " "packets out of the rest of the traffic.", ) variation_group.add_argument( "--listen-interface", type=str, help="Listen on this network interface." ) parser.add_argument( "--promiscuous-mode", action="store_true", help="Attempt to switch NIC to promiscuous mode. Depending on the " "network, this may make traffic of surrounding machines visible. " "Might require superuser privileges.", ) parser.add_argument( "--capture-file", metavar="", type=str, help="PCAP file with SNMP simulation data file to read from " "instead of listening on a NIC.", ) args = parser.parse_args() if not pcap: sys.stderr.write( "ERROR: pylibpcap package is missing!\r\nGet it by running " "`pip install " "https://downloads.sourceforge.net/project/pylibpcap/pylibpcap" "/0.6.4/pylibpcap-0.6.4.tar.gz`" "\r\n" ) parser.print_usage(sys.stderr) return 1 proc_name = os.path.basename(sys.argv[0]) try: log.set_logger(proc_name, *args.logging_method, force=True) if args.log_level: log.set_level(args.log_level) except error.SnmpsimError as exc: sys.stderr.write("%s\r\n%s\r\n" % exc) parser.print_usage(sys.stderr) sys.exit(1) if isinstance(args.start_object, rfc1902.ObjectIdentity) or isinstance( args.stop_object, rfc1902.ObjectIdentity ): mib_builder = builder.MibBuilder() mib_view_controller = view.MibViewController(mib_builder) compiler.addMibCompiler(mib_builder, sources=args.mib_sources) try: if isinstance(args.start_object, rfc1902.ObjectIdentity): args.start_object.resolveWithMib(mib_view_controller) if isinstance(args.stop_object, rfc1902.ObjectIdentity): args.stop_object.resolveWithMib(mib_view_controller) except PySnmpError as exc: sys.stderr.write("ERROR: %s\r\n" % exc) return 1 # Load variation module if args.variation_module: for variation_modules_dir in args.variation_modules_dir or confdir.variation: log.info( 'Scanning "%s" directory for variation ' "modules..." % variation_modules_dir ) if not os.path.exists(variation_modules_dir): log.info('Directory "%s" does not exist' % variation_modules_dir) continue mod = os.path.join(variation_modules_dir, args.variation_module + ".py") if not os.path.exists(mod): log.info('Variation module "%s" not found' % mod) continue ctx = {"path": mod, "moduleContext": {}} try: with open(mod) as fl: exec(compile(fl.read(), mod, "exec"), ctx) except Exception as exc: log.error('Variation module "%s" execution failure: %s' % (mod, exc)) return 1 variation_module = ctx log.info('Variation module "%s" loaded' % args.variation_module) break else: log.error('variation module "%s" not found' % args.variation_module) return 1 # Variation module initialization if variation_module: log.info("Initializing variation module...") for handler in ("init", "record", "shutdown"): if handler not in variation_module: log.error( 'missing "%s" handler at variation module ' '"%s"' % (handler, args.variation_module) ) return 1 handler = variation_module["init"] try: handler( options=args.variation_module_options, mode="recording", startOID=args.start_object, stopOID=args.stop_object, ) except Exception as exc: log.error( 'Variation module "%s" initialization ' "FAILED: %s" % (args.variation_module, exc) ) else: log.info('Variation module "%s" initialization OK' % args.variation_module) pcap_obj = pcap.pcapObject() if args.listen_interface: if not args.quiet: log.msg( "Listening on interface %s in %spromiscuous " "mode" % (args.listen_interface, "" if args.promiscuous_mode else "non-") ) try: pcap_obj.open_live( args.listen_interface, 65536, args.promiscuous_mode, 1000 ) except Exception as exc: log.error( "Error opening interface %s for snooping: " "%s" % (args.listen_interface, exc) ) return 1 elif args.capture_file: if not args.quiet: log.info("Opening capture file %s" % args.capture_file) try: pcap_obj.open_offline(args.capture_file) except Exception as exc: log.error( "Error opening capture file %s for reading: " "%s" % (args.capture_file, exc) ) return 1 else: sys.stderr.write("ERROR: no capture file or live interface specified\r\n") parser.print_usage(sys.stderr) return 1 if args.packet_filter: if not args.quiet: log.info('Applying packet filter "%s"' % args.packet_filter) pcap_obj.setfilter(args.packet_filter, 0, 0) if not args.quiet: log.info( "Processing records from %still " "%s" % ( "the beginning " if args.start_object else args.start_object, args.stop_object if args.stop_object else "the end", ) ) def parse_packet(raw): pkt = {} # http://www.tcpdump.org/linktypes.html ll_headers = {0: 4, 1: 14, 108: 4, 228: 0} if pcap_obj.datalink() in ll_headers: raw = raw[ll_headers[pcap_obj.datalink()] :] else: stats["unknown L2 protocol"] += 1 pkt["version"] = (ord(raw[0]) & 0xF0) >> 4 pkt["header_len"] = ord(raw[0]) & 0x0F pkt["tos"] = ord(raw[1]) pkt["total_len"] = socket.ntohs(struct.unpack("H", raw[2:4])[0]) pkt["id"] = socket.ntohs(struct.unpack("H", raw[4:6])[0]) pkt["flags"] = (ord(raw[6]) & 0xE0) >> 5 pkt["fragment_offset"] = socket.ntohs(struct.unpack("H", raw[6:8])[0] & 0x1F) pkt["ttl"] = ord(raw[8]) pkt["protocol"] = ord(raw[9]) pkt["checksum"] = socket.ntohs(struct.unpack("H", raw[10:12])[0]) pkt["source_address"] = pcap.ntoa(struct.unpack("i", raw[12:16])[0]) pkt["destination_address"] = pcap.ntoa(struct.unpack("i", raw[16:20])[0]) if pkt["header_len"] > 5: pkt["options"] = raw[20 : 4 * (pkt["header_len"] - 5)] else: pkt["options"] = None raw = raw[4 * pkt["header_len"] :] if pkt["protocol"] == 17: pkt["source_port"] = socket.ntohs(struct.unpack("H", raw[0:2])[0]) pkt["destination_port"] = socket.ntohs(struct.unpack("H", raw[2:4])[0]) raw = raw[8:] stats["UDP packets"] += 1 pkt["data"] = raw stats["IP packets"] += 1 return pkt def handle_snmp_message(d, t, private={}): msg_ver = api.decodeMessageVersion(d["data"]) if msg_ver in api.protoModules: p_mod = api.protoModules[msg_ver] else: stats["bad packets"] += 1 return try: rsp_msg, whole_msg = decoder.decode(d["data"], asn1Spec=p_mod.Message()) except PyAsn1Error: stats["bad packets"] += 1 return if rsp_msg["data"].getName() == "response": rsp_pdu = p_mod.apiMessage.get_pdu(rsp_msg) error_status = p_mod.apiPDU.get_error_status(rsp_pdu) if error_status: stats["SNMP errors"] += 1 else: endpoint = d["source_address"], d["source_port"] if endpoint not in endpoints: endpoints[endpoint] = udp.domainName + ( args.transport_id_offset + len(endpoints), ) stats["agents seen"] += 1 context = "{}/{}".format( p_mod.ObjectIdentifier(endpoints[endpoint]), p_mod.apiMessage.get_community(rsp_msg), ) if context not in contexts: contexts[context] = {} stats["contexts seen"] += 1 context = "{}/{}".format( p_mod.ObjectIdentifier(endpoints[endpoint]), p_mod.apiMessage.get_community(rsp_msg), ) stats["Response PDUs seen"] += 1 if "basetime" not in private: private["basetime"] = t for oid, value in p_mod.apiPDU.get_varbinds(rsp_pdu): if oid < args.start_object: continue if args.stop_object and oid >= args.stop_object: continue if oid in contexts[context]: if value != contexts[context][oid]: stats["snapshots taken"] += 1 else: contexts[context][oid] = [], [] contexts[context][oid][0].append(t - private["basetime"]) contexts[context][oid][1].append(value) stats["OIDs seen"] += 1 def handle_packet(pktlen, data, timestamp): if not data: stats["empty packets"] += 1 return handle_snmp_message(parse_packet(data), timestamp) try: if args.listen_interface: log.msg( 'Listening on interface "%s", kill me when you ' "are done." % args.listen_interface ) while True: pcap_obj.dispatch(1, handle_packet) elif args.capture_file: log.info('Processing capture file "%s"....' % args.capture_file) args = pcap_obj.next() while args: handle_packet(*args) args = pcap_obj.next() except (TypeError, KeyboardInterrupt): log.info("Shutting down process...") finally: data_file_handler = SnmprecRecord() for context in contexts: ext = os.path.extsep ext += RECORD_TYPES[args.destination_record_type].ext filename = os.path.join(args.output_dir, context + ext) if not args.quiet: log.info("Creating simulation context %s at %s" % (context, filename)) try: os.mkdir(os.path.dirname(filename)) except OSError: pass record = RECORD_TYPES[args.destination_record_type] try: output_file = record.open(filename, "wb") except OSError as exc: log.error(f"writing {filename}: {exc}") return 1 count = total = iteration = 0 time_offset = 0 req_time = time.time() oids = sorted(contexts[context]) oids.append(oids[-1]) # duplicate last OID to trigger stopFlag while True: for oid in oids: timeline, values = contexts[context][oid] value = values[ min(len(values) - 1, bisect.bisect_left(timeline, time_offset)) ] if value.tagSet in ( rfc1905.NoSuchObject.tagSet, rfc1905.NoSuchInstance.tagSet, rfc1905.EndOfMibView.tagSet, ): stats["SNMP exceptions"] += 1 continue # remove value enumeration if value.tagSet == Integer32.tagSet: value = Integer32(value) if value.tagSet == Unsigned32.tagSet: value = Unsigned32(value) if value.tagSet == Bits.tagSet: value = OctetString(value) # Build .snmprec record ctx = { "origOid": oid, "origValue": value, "count": count, "total": total, "iteration": iteration, "reqTime": req_time, "startOID": args.start_object, "stopOID": args.stop_object, "stopFlag": oids.index(oid) == len(oids) - 1, "variationModule": variation_module, } try: line = data_file_handler.format(oid, value, **ctx) except error.MoreDataNotification as exc: count = 0 iteration += 1 moreDataNotification = exc if "period" in moreDataNotification: time_offset += moreDataNotification["period"] log.info( "%s OIDs dumped, advancing time window to " "%.2f sec(s)..." % (total, time_offset) ) break except error.NoDataNotification: pass except error.SnmpsimError as exc: log.error(exc) continue else: output_file.write(line) count += 1 total += 1 else: break output_file.flush() output_file.close() if variation_module: log.info('Shutting down variation module "%s"...' % args.variation_module) handler = variation_module["shutdown"] try: handler(options=args.variation_module_options, mode="recording") except Exception as exc: log.error( 'Variation module "%s" shutdown FAILED: ' "%s" % (args.variation_module, exc) ) else: log.info('Variation module "%s" shutdown OK' % args.variation_module) log.info( """\ PCap statistics: packets snooped: %s packets dropped: %s packets dropped: by interface %s\ """ % pcap_obj.stats() ) log.info( """\ SNMP statistics: %s\ """ % " ".join(["%s: %s\r\n" % kv for kv in stats.items()]) ) return 0 if __name__ == "__main__": try: rc = main() except KeyboardInterrupt: sys.stderr.write("shutting down process...") rc = 0 except Exception as exc: sys.stderr.write("process terminated: %s" % exc) for line in traceback.format_exception(*sys.exc_info()): sys.stderr.write(line.replace("\n", ";")) rc = 1 sys.exit(rc) snmpsim-1.2.2/snmpsim/commands/rec2rec.py0000644000000000000000000002461613615410400015321 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP Simulator data file management tool # import argparse import functools import os import sys import traceback from pyasn1.type import univ from pysnmp.error import PySnmpError from pysnmp.smi import builder from pysnmp.smi import compiler from pysnmp.smi import rfc1902 from pysnmp.smi import view from snmpsim import error from snmpsim import utils from snmpsim.record import dump from snmpsim.record import mvc from snmpsim.record import sap from snmpsim.record import snmprec from snmpsim.record import walk from snmpsim.record.search.file import get_record class SnmprecRecordMixIn: def evaluateValue(self, oid, tag, value, **context): # Variation module reference if ":" in tag: context["backdoor"]["textTag"] = tag return oid, "", value else: return snmprec.SnmprecRecord.evaluate_value(self, oid, tag, value) def formatValue(self, oid, value, **context): if "textTag" in context["backdoor"]: return self.formatOid(oid), context["backdoor"]["textTag"], value else: return snmprec.SnmprecRecord.format_value(self, oid, value, **context) class SnmprecRecord(SnmprecRecordMixIn, snmprec.SnmprecRecord): pass class CompressedSnmprecRecord(SnmprecRecordMixIn, snmprec.CompressedSnmprecRecord): pass # data file types and parsers RECORD_TYPES = { dump.DumpRecord.ext: dump.DumpRecord(), mvc.MvcRecord.ext: mvc.MvcRecord(), sap.SapRecord.ext: sap.SapRecord(), walk.WalkRecord.ext: walk.WalkRecord(), SnmprecRecord.ext: SnmprecRecord(), CompressedSnmprecRecord.ext: CompressedSnmprecRecord(), } DESCRIPTION = ( "SNMP simulation data management and repair tool. Online " "documentation at https://www.pysnmp.com/snmpsim" ) def _parse_mib_object(arg, last=False): if "::" in arg: return rfc1902.ObjectIdentity(*arg.split("::", 1), last=last) else: return univ.ObjectIdentifier(arg) def main(): lost_comments = False written_count = skipped_count = duplicate_count = 0 broken_count = variation_count = 0 parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("-v", "--version", action="version", version=utils.TITLE) parser.add_argument( "--quiet", action="store_true", help="Do not print out informational messages" ) parser.add_argument( "--sort-records", action="store_true", help="Order simulation data records by OID", ) parser.add_argument( "--ignore-broken-records", action="store_true", help="Drop malformed simulation data records rather than bailing out", ) parser.add_argument( "--deduplicate-records", action="store_true", help="Drop duplicate simulation data records", ) parser.add_argument( "--escaped-strings", action="store_true", help='Produce Python-style escaped strings (e.g. "\x00") in ' "simulation data values rather than hexify such non-ASCII " "values", ) parser.add_argument( "--start-object", dest="start_oid", metavar="", type=_parse_mib_object, help="Drop all simulation data records prior to this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--stop-object", dest="stop_oid", metavar="", type=functools.partial(_parse_mib_object, last=True), help="Drop all simulation data records after this OID specified " "as MIB object (MIB::Object) or OID (1.3.6.)", ) parser.add_argument( "--mib-source", dest="mib_sources", metavar="", action="append", type=str, help="One or more URIs pointing to a collection of ASN.1 MIB files." 'Optional "@mib@" token gets replaced with desired MIB module ' "name during MIB search.", ) parser.add_argument( "--source-record-type", choices=RECORD_TYPES, default="snmprec", help="Treat input as simulation data of this record type", ) parser.add_argument( "--destination-record-type", choices=RECORD_TYPES, default="snmprec", help="Produce simulation data with record of this type", ) parser.add_argument( "--input-file", dest="input_files", metavar="", action="append", type=str, required=True, help="SNMP simulation data file to read records from", ) parser.add_argument( "--output-file", metavar="", type=str, help="SNMP simulation data file to write records to", ) args = parser.parse_args() if not args.mib_sources: args.mib_sources = ["https://mibs.pysnmp.com/asn1/@mib@"] args.input_files = [ RECORD_TYPES[args.source_record_type].open(x) for x in args.input_files ] if args.output_file and args.output_file != "-": ext = os.path.extsep + RECORD_TYPES[args.destination_record_type].ext if not args.output_file.endswith(ext): args.output_file += ext args.output_file = RECORD_TYPES[args.destination_record_type].open( args.output_file, "wb" ) else: args.output_file = sys.stdout if sys.version_info >= (3, 0, 0): # binary mode write args.output_file = sys.stdout.buffer elif sys.platform == "win32": import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) if not args.input_files: args.input_files.append(sys.stdin) if isinstance(args.start_oid, rfc1902.ObjectIdentity) or isinstance( args.stop_oid, rfc1902.ObjectIdentity ): mib_builder = builder.MibBuilder() mib_view_controller = view.MibViewController(mib_builder) compiler.addMibCompiler(mib_builder, sources=args.mib_sources) try: if isinstance(args.start_oid, rfc1902.ObjectIdentity): args.start_oid.resolveWithMib(mib_view_controller) if isinstance(args.stop_oid, rfc1902.ObjectIdentity): args.stop_oid.resolveWithMib(mib_view_controller) except PySnmpError as exc: sys.stderr.write("ERROR: %s\r\n" % exc) return 1 records_list = [] for input_file in args.input_files: if not args.quiet: sys.stderr.write( "# Input file #%s, processing records from %s till " "%s\r\n" % ( args.input_files.index(input_file), args.start_oid or "the beginning", args.stop_oid or "the end", ) ) line_no = 0 while True: line, rec_line_no, _ = get_record(input_file, line_no) if not line: break if rec_line_no != line_no + 1: if not args.quiet: sys.stderr.write( "# Losing comment at lines %s..%s (input file #" "%s)\r\n" % ( line_no + 1, rec_line_no - 1, args.input_files.index(input_file), ) ) line_no = rec_line_no lost_comments += 1 backdoor = {} try: oid, value = RECORD_TYPES[args.source_record_type].evaluate( line, backdoor=backdoor ) except error.SnmpsimError as exc: if args.ignore_broken_records: if not args.quiet: sys.stderr.write( "# Skipping broken record <%s>: " "%s\r\n" % (line, exc) ) broken_count += 1 continue else: if not args.quiet: sys.stderr.write( "ERROR: broken record <%s>: " "%s\r\n" % (line, exc) ) return 1 if ( args.start_oid and args.start_oid > oid or args.stop_oid and args.stop_oid < oid ): skipped_count += 1 continue records_list.append((oid, value, backdoor)) if args.sort_records: records_list.sort(key=lambda x: x[0]) unique_indices = set() for record in records_list: if args.deduplicate_records: if record[0] in unique_indices: if not args.quiet: sys.stderr.write( "# Skipping duplicate record " "<%s>\r\n" % record[0] ) duplicate_count += 1 continue else: unique_indices.add(record[0]) try: args.output_file.write( RECORD_TYPES[args.destination_record_type].format( record[0], record[1], backdoor=record[2], nohex=args.escaped_strings ) ) except Exception as exc: sys.stderr.write("ERROR: record not written: %s\r\n" % exc) break written_count += 1 if record[2]: variation_count += 1 if not args.quiet: sys.stderr.write( "# Records: written %s, filtered out %s, de-duplicated %s, ignored " "%s, broken %s, variated %s\r\n" % ( written_count, skipped_count, duplicate_count, lost_comments, broken_count, variation_count, ) ) args.output_file.flush() args.output_file.close() return 0 if __name__ == "__main__": try: rc = main() except KeyboardInterrupt: sys.stderr.write("shutting down process...") rc = 0 except Exception: sys.stderr.write("process terminated: %s" % sys.exc_info()[1]) for line in traceback.format_exception(*sys.exc_info()): sys.stderr.write(line.replace("\n", ";")) rc = 1 sys.exit(rc) snmpsim-1.2.2/snmpsim/commands/responder.py0000644000000000000000000010634413615410400015774 0ustar00# # This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # Copyright (c) 2022-2025, LeXtudio Inc. # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP Agent Simulator: fully-fledged SNMP v1/v2c/v3 command responder # import argparse import asyncio import functools import os import sys import traceback from hashlib import md5 from pyasn1 import debug as pyasn1_debug from pyasn1.type import univ from pysnmp import debug as pysnmp_debug from pysnmp import error from pysnmp.carrier.asyncio.dgram import udp from pysnmp.carrier.asyncio.dgram import udp6 from pysnmp.carrier.asyncio.dispatch import AsyncioDispatcher from pysnmp.entity import config from pysnmp.entity import engine from pysnmp.entity.rfc3413 import cmdrsp from pysnmp.entity.rfc3413 import context from snmpsim import confdir from snmpsim import controller from snmpsim import daemon from snmpsim import datafile from snmpsim import endpoints from snmpsim import log from snmpsim import utils from snmpsim import variation from snmpsim.error import NoDataNotification from snmpsim.error import SnmpsimError from snmpsim.reporting.manager import ReportingManager AUTH_PROTOCOLS = { "MD5": config.USM_AUTH_HMAC96_MD5, "SHA": config.USM_AUTH_HMAC96_SHA, "SHA224": config.USM_AUTH_HMAC128_SHA224, "SHA256": config.USM_AUTH_HMAC192_SHA256, "SHA384": config.USM_AUTH_HMAC256_SHA384, "SHA512": config.USM_AUTH_HMAC384_SHA512, "NONE": config.USM_AUTH_NONE, } PRIV_PROTOCOLS = { "DES": config.USM_PRIV_CBC56_DES, "3DES": config.USM_PRIV_CBC168_3DES, "AES": config.USM_PRIV_CFB128_AES, "AES128": config.USM_PRIV_CFB128_AES, "AES192": config.USM_PRIV_CFB192_AES, "AES192BLMT": config.USM_PRIV_CFB192_AES_BLUMENTHAL, "AES256": config.USM_PRIV_CFB256_AES, "AES256BLMT": config.USM_PRIV_CFB256_AES_BLUMENTHAL, "NONE": config.USM_PRIV_NONE, } DESCRIPTION = ( "SNMP agent simulator: responds to SNMP v1/v2c/v3 requests, variate " "responses based on transport addresses, SNMP community name, SNMPv3 " "context or via variation modules." ) V3_OPTIONS = "SNMPv3 options" def probe_hash_context(responder, snmp_engine): """v3arch SNMP context name searcher""" execCtx = snmp_engine.observer.getExecutionContext("rfc3412.receiveMessage:request") (transport_domain, transport_address, context_engine_id, context_name) = ( execCtx["transportDomain"], execCtx["transportAddress"], execCtx["contextEngineId"], execCtx["contextName"].prettyPrint(), ) if context_engine_id == snmp_engine.snmpEngineID: context_engine_id = datafile.SELF_LABEL else: context_engine_id = context_engine_id.prettyPrint() for candidate in datafile.probe_context( transport_domain, transport_address, context_engine_id, context_name ): if len(candidate) > 32: probed_context_name = md5(candidate).hexdigest() else: probed_context_name = candidate try: mib_instrum = responder.snmpContext.getMibInstrum(probed_context_name) except error.PySnmpError: pass else: log.info( "Using %s selected by candidate %s; transport ID %s, " "source address %s, context engine ID %s, " "community name " '"%s"' % ( mib_instrum, candidate, univ.ObjectIdentifier(transport_domain), transport_address[0], context_engine_id, probed_context_name, ) ) context_name = probed_context_name break else: mib_instrum = responder.snmpContext.getMibInstrum(context_name) log.info( 'Using %s selected by contextName "%s", transport ID %s, ' "source address %s" % ( mib_instrum, context_name, univ.ObjectIdentifier(transport_domain), transport_address[0], ) ) if not isinstance( mib_instrum, (controller.MibInstrumController, controller.DataIndexInstrumController), ): log.error("LCD access denied (contextName does not match any data file)") raise NoDataNotification() return context_name class GetCommandResponder(cmdrsp.GetCommandResponder): """v3arch GET command handler""" def handleMgmtOperation( self, snmp_engine, state_reference, context_name, pdu, ac_info ): try: cmdrsp.GetCommandResponder.handleMgmtOperation( self, snmp_engine, state_reference, probe_hash_context(self, snmp_engine), pdu, (None, snmp_engine), # custom acInfo ) except NoDataNotification: self.releaseStateInformation(state_reference) class SetCommandResponder(cmdrsp.SetCommandResponder): """v3arch SET command handler""" def handleMgmtOperation( self, snmp_engine, state_reference, context_name, pdu, ac_info ): try: cmdrsp.SetCommandResponder.handleMgmtOperation( self, snmp_engine, state_reference, probe_hash_context(self, snmp_engine), pdu, (None, snmp_engine), # custom acInfo ) except NoDataNotification: self.releaseStateInformation(state_reference) class NextCommandResponder(cmdrsp.NextCommandResponder): """v3arch GETNEXT command handler""" def handleMgmtOperation( self, snmp_engine, state_reference, context_name, pdu, ac_info ): try: cmdrsp.NextCommandResponder.handleMgmtOperation( self, snmp_engine, state_reference, probe_hash_context(self, snmp_engine), pdu, (None, snmp_engine), # custom acInfo ) except NoDataNotification: self.releaseStateInformation(state_reference) class BulkCommandResponder(cmdrsp.BulkCommandResponder): """v3arch GETBULK command handler""" def handleMgmtOperation( self, snmp_engine, state_reference, context_name, pdu, ac_info ): try: cmdrsp.BulkCommandResponder.handleMgmtOperation( self, snmp_engine, state_reference, probe_hash_context(self, snmp_engine), pdu, (None, snmp_engine), # custom acInfo ) except NoDataNotification: self.releaseStateInformation(state_reference) def _parse_sized_string(arg, min_length=8): if len(arg) < min_length: raise argparse.ArgumentTypeError( f'Value "{arg}" must be {min_length}+ chars of length' ) return arg def main(): # Python 3.14+ no longer auto-creates a default event loop. try: asyncio.get_event_loop() except RuntimeError: asyncio.set_event_loop(asyncio.new_event_loop()) parser = argparse.ArgumentParser(add_help=False) parser.add_argument("-v", "--version", action="version", version=utils.TITLE) parser.add_argument( "-h", action="store_true", dest="usage", help="Brief usage message" ) parser.add_argument("--help", action="store_true", help="Detailed help message") parser.add_argument( "--quiet", action="store_true", help="Do not print out informational messages" ) parser.add_argument( "--debug", choices=pysnmp_debug.flagMap, action="append", type=str, default=[], help="Enable one or more categories of SNMP debugging.", ) parser.add_argument( "--debug-asn1", choices=pyasn1_debug.FLAG_MAP, action="append", type=str, default=[], help="Enable one or more categories of ASN.1 debugging.", ) parser.add_argument( "--logging-method", type=lambda x: x.split(":"), metavar="=<%s[:args]>]" % "|".join(log.METHODS_MAP), default="stderr", help="Logging method.", ) parser.add_argument( "--log-level", choices=log.LEVELS_MAP, type=str, default="info", help="Logging level.", ) parser.add_argument( "--reporting-method", type=lambda x: x.split(":"), metavar="=<%s[:args]>]" % "|".join(ReportingManager.REPORTERS), default="null", help="Activity metrics reporting method.", ) parser.add_argument( "--daemonize", action="store_true", help="Disengage from controlling terminal and become a daemon", ) parser.add_argument( "--process-user", type=str, help="If run as root, switch simulator daemon to this user right " "upon binding privileged ports", ) parser.add_argument( "--process-group", type=str, help="If run as root, switch simulator daemon to this group right " "upon binding privileged ports", ) parser.add_argument( "--pid-file", metavar="", type=str, default=f"/var/run/{__name__}/{os.getpid()}.pid", help="SNMP simulation data file to write records to", ) parser.add_argument( "--cache-dir", metavar="", type=str, help="Location for SNMP simulation data file indices to create", ) parser.add_argument( "--force-index-rebuild", action="store_true", help="Rebuild simulation data files indices even if they seem " "up to date", ) parser.add_argument( "--validate-data", action="store_true", help="Validate simulation data files on daemon start-up", ) parser.add_argument( "--variation-modules-dir", metavar="", type=str, action="append", default=[], help="Variation modules search path(s)", ) parser.add_argument( "--variation-module-options", metavar="", type=str, action="append", default=[], help="Options for a specific variation module", ) parser.add_argument( "--v3-only", action="store_true", help="Trip legacy SNMP v1/v2c support to gain a little lesser memory " "footprint", ) parser.add_argument( "--transport-id-offset", type=int, default=0, help="Start numbering the last sub-OID of transport endpoint OIDs " "starting from this ID", ) parser.add_argument( "--max-var-binds", type=int, default=64, help="Maximum number of variable bindings to include in a single " "response", ) parser.add_argument( "--args-from-file", metavar="", type=str, help="Read SNMP engine(s) command-line configuration from this " "file. Can be useful when command-line is too long", ) # We do not parse SNMP params with argparse, but we want its -h/--help snmp_helper = argparse.ArgumentParser( description=DESCRIPTION, add_help=False, parents=[parser] ) v3_usage = """\ Configure one or more independent SNMP engines. Each SNMP engine has a distinct engine ID, its own set of SNMP USM users, one or more network transport endpoints to listen on and its own simulation data directory. Each SNMP engine configuration starts with `--v3-engine-id ` parameter followed by other configuration options up to the next `--v3-engine-id` option or end of command line Example ------- $ snmp-command-responder \\ --v3-engine-id auto \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1024 \\ --v3-engine-id auto \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1025 \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1026 Besides network endpoints, simulated agents can be addressed by SNMPv1/v2c community name or SNMPv3 context engine ID/name. These parameters are configured automatically based on simulation data file paths relative to `--data-dir`. """ v3_group = snmp_helper.add_argument_group(v3_usage) v3_group.add_argument( "--v3-engine-id", type=str, metavar="", default="auto", help="SNMPv3 engine ID", ) v3_group.add_argument( "--v3-user", metavar="", type=functools.partial(_parse_sized_string, min_length=1), help="SNMPv3 USM user (security) name", ) v3_group.add_argument( "--v3-auth-key", type=_parse_sized_string, help="SNMPv3 USM authentication key (must be > 8 chars)", ) v3_group.add_argument( "--v3-auth-proto", choices=AUTH_PROTOCOLS, type=lambda x: x.upper(), default="NONE", help="SNMPv3 USM authentication protocol", ) v3_group.add_argument( "--v3-priv-key", type=_parse_sized_string, help="SNMPv3 USM privacy (encryption) key (must be > 8 chars)", ) v3_group.add_argument( "--v3-priv-proto", choices=PRIV_PROTOCOLS, type=lambda x: x.upper(), default="NONE", help="SNMPv3 USM privacy (encryption) protocol", ) v3_group.add_argument( "--v3-context-engine-id", type=lambda x: univ.OctetString(hexValue=x[2:]), help="SNMPv3 context engine ID", ) v3_group.add_argument( "--v3-context-name", type=str, default="", help="SNMPv3 context engine ID" ) v3_group.add_argument( "--agent-udpv4-endpoint", type=endpoints.parse_endpoint, metavar="<[X.X.X.X]:NNNNN>", help="SNMP agent UDP/IPv4 address to listen on (name:port)", ) v3_group.add_argument( "--agent-udpv6-endpoint", type=functools.partial(endpoints.parse_endpoint, ipv6=True), metavar="<[X:X:..X]:NNNNN>", help="SNMP agent UDP/IPv6 address to listen on ([name]:port)", ) v3_group.add_argument( "--data-dir", type=str, metavar="", help="SNMP simulation data recordings directory.", ) v3_group.add_argument( "--timeout", type=float, default=0, help="SNMP simulation process timeout", ) args, unparsed_args = parser.parse_known_args() if args.usage: snmp_helper.print_usage(sys.stderr) return 1 if args.help: snmp_helper.print_help(sys.stderr) return 1 _, unknown_args = snmp_helper.parse_known_args(unparsed_args) if unknown_args: sys.stderr.write( "ERROR: Unknown command-line parameter(s) " "%s\r\n" % " ".join(unknown_args) ) snmp_helper.print_usage(sys.stderr) return 1 # Reformat unparsed args into a list of (option, value) tuples snmp_args = [] name = None for opt in unparsed_args: if "=" in opt: snmp_args.append(opt.split("=")) elif name: snmp_args.append((name, opt)) name = None else: name = opt if name: sys.stderr.write( "ERROR: Non-paired command-line key-value parameter " "%s\r\n" % name ) snmp_helper.print_usage(sys.stderr) return 1 if args.debug: pysnmp_debug.set_logger(pysnmp_debug.Debug(*args.debug)) if args.debug_asn1: pyasn1_debug.setLogger(pyasn1_debug.Debug(*args.debug_asn1)) if args.cache_dir: confdir.cache = args.cache_dir if args.variation_modules_dir: confdir.variation = args.variation_modules_dir variation_modules_options = variation.parse_modules_options( args.variation_module_options ) if args.args_from_file: try: with open(args.args_from_file) as fl: snmp_args.extend( [handler.split("=", 1) for handler in fl.read().split()] ) except Exception as exc: sys.stderr.write( "ERROR: file %s opening failure: " "%s\r\n" % (args.args_from_file, exc) ) snmp_helper.print_usage(sys.stderr) return 1 with daemon.PrivilegesOf(args.process_user, args.process_group): proc_name = os.path.basename(sys.argv[0]) try: log.set_logger(proc_name, *args.logging_method, force=True) if args.log_level: log.set_level(args.log_level) except SnmpsimError as exc: sys.stderr.write("%s\r\n" % exc) snmp_helper.print_usage(sys.stderr) return 1 try: ReportingManager.configure(*args.reporting_method) except SnmpsimError as exc: sys.stderr.write("%s\r\n" % exc) snmp_helper.print_usage(sys.stderr) return 1 if args.daemonize: try: daemon.daemonize(args.pid_file) except Exception as exc: sys.stderr.write("ERROR: cant daemonize process: %s\r\n" % exc) snmp_helper.print_usage(sys.stderr) return 1 if not os.path.exists(confdir.cache): try: with daemon.PrivilegesOf(args.process_user, args.process_group): os.makedirs(confdir.cache) except OSError as exc: log.error( 'failed to create cache directory "%s": ' "%s" % (confdir.cache, exc) ) return 1 else: log.info('Cache directory "%s" created' % confdir.cache) variation_modules = variation.load_variation_modules( confdir.variation, variation_modules_options ) with daemon.PrivilegesOf(args.process_user, args.process_group): variation.initialize_variation_modules(variation_modules, mode="variating") def configure_managed_objects( data_dirs, data_index_instrum_controller, snmp_engine=None, snmp_context=None ): """Build pysnmp Managed Objects base from data files information""" _mib_instrums = {} _data_files = {} for dataDir in data_dirs: log.info( 'Scanning "%s" directory for %s data ' "files..." % ( dataDir, ",".join( [ f" *{os.path.extsep}{x.ext}" for x in variation.RECORD_TYPES.values() ] ), ) ) if not os.path.exists(dataDir): log.info('Directory "%s" does not exist' % dataDir) continue log.msg.inc_ident() for full_path, text_parser, community_name in datafile.get_data_files( dataDir ): if community_name in _data_files: log.error( 'ignoring duplicate Community/ContextName "%s" for data ' "file %s (%s already loaded)" % (community_name, full_path, _data_files[community_name]) ) continue elif full_path in _mib_instrums: mib_instrum = _mib_instrums[full_path] log.info(f"Configuring *shared* {mib_instrum}") else: data_file = datafile.DataFile( full_path, text_parser, variation_modules ) data_file.index_text(args.force_index_rebuild, args.validate_data) MibController = controller.MIB_CONTROLLERS[data_file.layout] mib_instrum = MibController(data_file) _mib_instrums[full_path] = mib_instrum _data_files[community_name] = full_path log.info(f"Configuring {mib_instrum}") log.info(f"SNMPv1/2c community name: {community_name}") agent_name = md5( univ.OctetString(community_name).asOctets() ).hexdigest() context_name = agent_name if not args.v3_only: # snmpCommunityTable::snmpCommunityIndex can't be > 32 if snmp_engine: # Add check for snmp_engine config.add_v1_system( snmp_engine, agent_name, community_name, contextName=context_name, ) snmp_context.register_context_name(context_name, mib_instrum) if len(community_name) <= 32: snmp_context.register_context_name(community_name, mib_instrum) data_index_instrum_controller.add_data_file( full_path, community_name, context_name ) log.info( "SNMPv3 Context Name: %s" "%s" % ( context_name, len(community_name) <= 32 and " or %s" % community_name or "", ) ) log.msg.dec_ident() del _mib_instrums del _data_files # Bind transport endpoints for idx, opt in enumerate(snmp_args): if opt[0] == "--agent-udpv4-endpoint": snmp_args[idx] = (opt[0], endpoints.IPv4TransportEndpoints().add(opt[1])) elif opt[0] == "--agent-udpv6-endpoint": snmp_args[idx] = (opt[0], endpoints.IPv6TransportEndpoints().add(opt[1])) # Start configuring SNMP engine(s) transport_dispatcher = AsyncioDispatcher() transport_dispatcher.register_routing_callback(lambda td, t, d: td) if not snmp_args or snmp_args[0][0] != "--v3-engine-id": snmp_args.insert(0, ("--v3-engine-id", "auto")) if snmp_args and snmp_args[-1][0] != "end-of-options": snmp_args.append(("end-of-options", "")) snmp_engine = None timeout = 0 # Initialize timeout before the loop transport_index = { "udpv4": args.transport_id_offset, "udpv6": args.transport_id_offset, } for opt in snmp_args: if opt[0] in ("--v3-engine-id", "end-of-options"): if snmp_engine: log.info("--- SNMP Engine configuration") log.info( "SNMPv3 EngineID: " "%s" % ( hasattr(snmp_engine, "snmpEngineID") and snmp_engine.snmpEngineID.prettyPrint() or "", ) ) if not v3_context_engine_ids: v3_context_engine_ids.append((None, [])) log.msg.inc_ident() log.info("--- Simulation data recordings configuration") for v3_context_engine_id, ctx_data_dirs in v3_context_engine_ids: snmp_context = context.SnmpContext( snmp_engine, v3_context_engine_id ) # unregister default context snmp_context.unregister_context_name(b"") log.info( "SNMPv3 Context Engine ID: " "%s" % snmp_context.contextEngineId.prettyPrint() ) data_index_instrum_controller = ( controller.DataIndexInstrumController() ) with daemon.PrivilegesOf(args.process_user, args.process_group): configure_managed_objects( ctx_data_dirs or data_dirs or confdir.data, data_index_instrum_controller, snmp_engine, snmp_context, ) # Configure access to data index config.add_v1_system(snmp_engine, "index", "index", contextName="index") log.info("--- SNMPv3 USM configuration") if not v3_users: v3_users = ["simulator"] v3_auth_keys[v3_users[0]] = "auctoritas" v3_auth_protos[v3_users[0]] = "MD5" v3_priv_keys[v3_users[0]] = "privatus" v3_priv_protos[v3_users[0]] = "DES" for v3User in v3_users: if v3User in v3_auth_keys: if v3User not in v3_auth_protos: v3_auth_protos[v3User] = "MD5" elif v3User in v3_auth_protos: log.error( "auth protocol configured without key for user " "%s" % v3User ) return 1 else: v3_auth_keys[v3User] = None v3_auth_protos[v3User] = "NONE" if v3User in v3_priv_keys: if v3User not in v3_priv_protos: v3_priv_protos[v3User] = "DES" elif v3User in v3_priv_protos: log.error( "privacy protocol configured without key for user " "%s" % v3User ) return 1 else: v3_priv_keys[v3User] = None v3_priv_protos[v3User] = "NONE" if ( AUTH_PROTOCOLS[v3_auth_protos[v3User]] == config.USM_AUTH_NONE and PRIV_PROTOCOLS[v3_priv_protos[v3User]] != config.USM_PRIV_NONE ): log.error( "privacy impossible without authentication for USM user " "%s" % v3User ) return 1 try: config.add_v3_user( snmp_engine, v3User, AUTH_PROTOCOLS[v3_auth_protos[v3User]], v3_auth_keys[v3User], PRIV_PROTOCOLS[v3_priv_protos[v3User]], v3_priv_keys[v3User], ) except error.PySnmpError as exc: log.error("bad USM values for user %s: " "%s" % (v3User, exc)) return 1 log.info("SNMPv3 USM SecurityName: %s" % v3User) if AUTH_PROTOCOLS[v3_auth_protos[v3User]] != config.USM_AUTH_NONE: log.info( "SNMPv3 USM authentication key: %s, " "authentication protocol: " "%s" % (v3_auth_keys[v3User], v3_auth_protos[v3User]) ) if PRIV_PROTOCOLS[v3_priv_protos[v3User]] != config.USM_PRIV_NONE: log.info( "SNMPv3 USM encryption (privacy) key: %s, " "encryption protocol: " "%s" % (v3_priv_keys[v3User], v3_priv_protos[v3User]) ) snmp_context.register_context_name( "index", data_index_instrum_controller ) log.info( "Maximum number of variable bindings in SNMP response: " "%s" % local_max_var_binds ) log.info("--- Transport configuration") if not agent_udpv4_endpoints and not agent_udpv6_endpoints: log.error( "agent endpoint address(es) not specified for SNMP " "engine ID %s" % v3_engine_id ) return 1 for agent_udpv4_endpoint in agent_udpv4_endpoints: transport_domain = udp.DOMAIN_NAME + (transport_index["udpv4"],) transport_index["udpv4"] += 1 snmp_engine.register_transport_dispatcher( transport_dispatcher, transport_domain ) config.add_transport( snmp_engine, transport_domain, agent_udpv4_endpoint[0] ) log.msg( "Listening at UDP/IPv4 endpoint %s, transport ID " "%s" % ( agent_udpv4_endpoint[1], ".".join([str(handler) for handler in transport_domain]), ) ) for agent_udpv6_endpoint in agent_udpv6_endpoints: transport_domain = udp6.domainName + (transport_index["udpv6"],) transport_index["udpv6"] += 1 snmp_engine.register_transport_dispatcher( transport_dispatcher, transport_domain ) config.add_transport( snmp_engine, transport_domain, agent_udpv6_endpoint[0] ) log.msg( "Listening at UDP/IPv6 endpoint %s, transport ID " "%s" % ( agent_udpv6_endpoint[1], ".".join([str(handler) for handler in transport_domain]), ) ) # SNMP applications GetCommandResponder(snmp_engine, snmp_context) SetCommandResponder(snmp_engine, snmp_context) NextCommandResponder(snmp_engine, snmp_context) BulkCommandResponder( snmp_engine, snmp_context ).maxVarBinds = local_max_var_binds log.msg.dec_ident() if opt[0] == "end-of-options": # Load up the rest of MIBs while running privileged ( snmp_engine.message_dispatcher.mib_instrum_controller.get_mib_builder().load_modules() ) break # Prepare for next engine ID configuration v3_context_engine_ids = [] data_dirs = [] local_max_var_binds = args.max_var_binds v3_users = [] v3_auth_keys = {} v3_auth_protos = {} v3_priv_keys = {} v3_priv_protos = {} agent_udpv4_endpoints = [] agent_udpv6_endpoints = [] # timeout = 0 <- Remove initialization from here try: v3_engine_id = opt[1] if not v3_engine_id or v3_engine_id.lower() == "auto": snmp_engine = engine.SnmpEngine() else: snmp_engine = engine.SnmpEngine( snmpEngineID=univ.OctetString(hexValue=v3_engine_id) ) except Exception as exc: log.error( 'SNMPv3 Engine initialization failed, EngineID "%s": ' "%s" % (v3_engine_id, exc) ) return 1 config.add_context(snmp_engine, "") elif opt[0] == "--v3-context-engine-id": v3_context_engine_ids.append((univ.OctetString(hexValue=opt[1]), [])) elif opt[0] == "--data-dir": if v3_context_engine_ids: v3_context_engine_ids[-1][1].append(opt[1]) else: data_dirs.append(opt[1]) elif opt[0] == "--max-varbinds": local_max_var_binds = opt[1] elif opt[0] == "--v3-user": v3_users.append(opt[1]) elif opt[0] == "--v3-auth-key": if not v3_users: log.error("--v3-user should precede %s" % opt[0]) return 1 if v3_users[-1] in v3_auth_keys: log.error(f"repetitive {opt[0]} option for user {v3_users[-1]}") return 1 v3_auth_keys[v3_users[-1]] = opt[1] elif opt[0] == "--v3-auth-proto": if opt[1].upper() not in AUTH_PROTOCOLS: log.error("bad v3 auth protocol %s" % opt[1]) return 1 else: if not v3_users: log.error("--v3-user should precede %s" % opt[0]) return 1 if v3_users[-1] in v3_auth_protos: log.error(f"repetitive {opt[0]} option for user {v3_users[-1]}") return 1 v3_auth_protos[v3_users[-1]] = opt[1].upper() elif opt[0] == "--v3-priv-key": if not v3_users: log.error("--v3-user should precede %s" % opt[0]) return 1 if v3_users[-1] in v3_priv_keys: log.error(f"repetitive {opt[0]} option for user {v3_users[-1]}") return 1 v3_priv_keys[v3_users[-1]] = opt[1] elif opt[0] == "--v3-priv-proto": if opt[1].upper() not in PRIV_PROTOCOLS: log.error("bad v3 privacy protocol %s" % opt[1]) return 1 else: if not v3_users: log.error("--v3-user should precede %s" % opt[0]) return 1 if v3_users[-1] in v3_priv_protos: log.error(f"repetitive {opt[0]} option for user {v3_users[-1]}") return 1 v3_priv_protos[v3_users[-1]] = opt[1].upper() elif opt[0] == "--agent-udpv4-endpoint": agent_udpv4_endpoints.append(opt[1]) elif opt[0] == "--agent-udpv6-endpoint": agent_udpv6_endpoints.append(opt[1]) elif opt[0] == "--timeout": timeout = opt[1] transport_dispatcher.job_started(1) # server job would never finish with daemon.PrivilegesOf(args.process_user, args.process_group, final=True): try: transport_dispatcher.run_dispatcher(float(timeout)) except KeyboardInterrupt: log.info("Shutting down process...") finally: if variation_modules: log.info("Shutting down variation modules:") for name, contexts in variation_modules.items(): body = contexts[0] try: body["shutdown"](options=body["args"], mode="variation") except Exception as exc: log.error( 'Variation module "%s" shutdown FAILED: ' "%s" % (name, exc) ) else: log.info('Variation module "%s" shutdown OK' % name) transport_dispatcher.close_dispatcher() log.info("Process terminated") return 0 if __name__ == "__main__": try: rc = main() except KeyboardInterrupt: sys.stderr.write("shutting down process...") rc = 0 except Exception as exc: sys.stderr.write("process terminated: %s" % exc) for line in traceback.format_exception(*sys.exc_info()): sys.stderr.write(line.replace("\n", ";")) rc = 1 sys.exit(rc) snmpsim-1.2.2/snmpsim/commands/responder_lite.py0000644000000000000000000004624013615410400017007 0ustar00# This file is part of snmpsim software. # # Copyright (c) 2010-2019, Ilya Etingof # Copyright (c) 2022-2025, LeXtudio Inc. # License: https://www.pysnmp.com/snmpsim/license.html # # SNMP Agent Simulator: lightweight SNMP v1/v2c command responder # import argparse import asyncio import os import sys import traceback from pyasn1 import debug as pyasn1_debug from pyasn1.codec.ber import decoder from pyasn1.codec.ber import encoder from pyasn1.type import univ from pysnmp import debug as pysnmp_debug from pysnmp.carrier.asyncio.dgram import udp from pysnmp.carrier.asyncio.dgram import udp6 from pysnmp.carrier.asyncio.dispatch import AsyncioDispatcher from pysnmp.proto import api from pysnmp.proto import rfc1902 from pysnmp.proto import rfc1905 from snmpsim import confdir from snmpsim import controller from snmpsim import daemon from snmpsim import datafile from snmpsim import endpoints from snmpsim import log from snmpsim import utils from snmpsim import variation from snmpsim.error import NoDataNotification from snmpsim.error import SnmpsimError from snmpsim.reporting.manager import ReportingManager SNMP_2TO1_ERROR_MAP = { rfc1902.Counter64.tagSet: 5, rfc1905.NoSuchObject.tagSet: 2, rfc1905.NoSuchInstance.tagSet: 2, rfc1905.EndOfMibView.tagSet: 2, } DESCRIPTION = ( "Lightweight SNMP agent simulator: responds to SNMP v1/v2c requests, " "variate responses based on transport addresses, SNMP community name " "or via variation modules." ) def main(): # Python 3.14+ no longer auto-creates a default event loop. try: asyncio.get_event_loop() except RuntimeError: asyncio.set_event_loop(asyncio.new_event_loop()) parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("-v", "--version", action="version", version=utils.TITLE) parser.add_argument( "--quiet", action="store_true", help="Do not print out informational messages" ) parser.add_argument( "--debug", choices=pysnmp_debug.flagMap, action="append", type=str, default=[], help="Enable one or more categories of SNMP debugging.", ) parser.add_argument( "--debug-asn1", choices=pyasn1_debug.FLAG_MAP, action="append", type=str, default=[], help="Enable one or more categories of ASN.1 debugging.", ) parser.add_argument( "--logging-method", type=lambda x: x.split(":"), metavar="=<%s[:args]>]" % "|".join(log.METHODS_MAP), default="stderr", help="Logging method.", ) parser.add_argument( "--log-level", choices=log.LEVELS_MAP, type=str, default="info", help="Logging level.", ) parser.add_argument( "--reporting-method", type=lambda x: x.split(":"), metavar="=<%s[:args]>]" % "|".join(ReportingManager.REPORTERS), default="null", help="Activity metrics reporting method.", ) parser.add_argument( "--daemonize", action="store_true", help="Disengage from controlling terminal and become a daemon", ) parser.add_argument( "--process-user", type=str, help="If run as root, switch simulator daemon to this user right " "upon binding privileged ports", ) parser.add_argument( "--process-group", type=str, help="If run as root, switch simulator daemon to this group right " "upon binding privileged ports", ) parser.add_argument( "--pid-file", metavar="", type=str, default=f"/var/run/{__name__}/{os.getpid()}.pid", help="SNMP simulation data file to write records to", ) parser.add_argument( "--cache-dir", metavar="", type=str, help="Location for SNMP simulation data file indices to create", ) parser.add_argument( "--force-index-rebuild", action="store_true", help="Rebuild simulation data files indices even if they seem up to date", ) parser.add_argument( "--validate-data", action="store_true", help="Validate simulation data files on daemon start-up", ) parser.add_argument( "--variation-modules-dir", metavar="", type=str, action="append", default=[], help="Variation modules search path(s)", ) parser.add_argument( "--variation-module-options", metavar="", type=str, action="append", default=[], help="Options for a specific variation module", ) parser.add_argument( "--v2c-arch", action="store_true", help="Use lightweight, legacy SNMP architecture capable to support " "v1/v2c versions of SNMP", ) parser.add_argument( "--v3-only", action="store_true", help="Trip legacy SNMP v1/v2c support to gain a little lesser memory footprint", ) parser.add_argument( "--transport-id-offset", type=int, default=0, help="Start numbering the last sub-OID of transport endpoint OIDs " "starting from this ID", ) parser.add_argument( "--max-var-binds", type=int, default=64, help="Maximum number of variable bindings to include in a single response", ) parser.add_argument( "--data-dir", type=str, action="append", metavar="", dest="data_dirs", help="SNMP simulation data recordings directory.", ) endpoint_group = parser.add_mutually_exclusive_group(required=True) endpoint_group.add_argument( "--agent-udpv4-endpoint", type=str, action="append", metavar="<[X.X.X.X]:NNNNN>", dest="agent_udpv4_endpoints", default=[], help="SNMP agent UDP/IPv4 address to listen on (name:port)", ) endpoint_group.add_argument( "--agent-udpv6-endpoint", type=str, action="append", metavar="<[X:X:..X]:NNNNN>", dest="agent_udpv6_endpoints", default=[], help="SNMP agent UDP/IPv6 address to listen on ([name]:port)", ) args = parser.parse_args() if args.debug: pysnmp_debug.setLogger(pysnmp_debug.Debug(*args.debug)) if args.debug_asn1: pyasn1_debug.setLogger(pyasn1_debug.Debug(*args.debug_asn1)) if args.cache_dir: confdir.cache = args.cache_dir if args.variation_modules_dir: confdir.variation = args.variation_modules_dir variation_modules_options = variation.parse_modules_options( args.variation_module_options ) with daemon.PrivilegesOf(args.process_user, args.process_group): proc_name = os.path.basename(sys.argv[0]) try: log.set_logger(proc_name, *args.logging_method, force=True) if args.log_level: log.set_level(args.log_level) except SnmpsimError as exc: sys.stderr.write("%s\r\n" % exc) parser.print_usage(sys.stderr) return 1 try: ReportingManager.configure(*args.reporting_method) except SnmpsimError as exc: sys.stderr.write("%s\r\n" % exc) parser.print_usage(sys.stderr) return 1 if args.daemonize: try: daemon.daemonize(args.pid_file) except Exception as exc: sys.stderr.write("ERROR: cant daemonize process: %s\r\n" % exc) parser.print_usage(sys.stderr) return 1 if not os.path.exists(confdir.cache): try: with daemon.PrivilegesOf(args.process_user, args.process_group): os.makedirs(confdir.cache) except OSError as exc: log.error( 'failed to create cache directory "%s": %s' % (confdir.cache, exc) ) return 1 else: log.info('Cache directory "%s" created' % confdir.cache) variation_modules = variation.load_variation_modules( confdir.variation, variation_modules_options ) with daemon.PrivilegesOf(args.process_user, args.process_group): variation.initialize_variation_modules(variation_modules, mode="variating") def configure_managed_objects( data_dirs, data_index_instrum_controller, snmp_engine=None, snmp_context=None ): """Build pysnmp Managed Objects base from data files information""" _mib_instrums = {} _data_files = {} for dataDir in data_dirs: log.info( 'Scanning "%s" directory for %s data ' "files..." % ( dataDir, ",".join( [ f" *{os.path.extsep}{x.ext}" for x in variation.RECORD_TYPES.values() ] ), ) ) if not os.path.exists(dataDir): log.info('Directory "%s" does not exist' % dataDir) continue log.msg.inc_ident() for full_path, text_parser, community_name in datafile.get_data_files( dataDir ): if community_name in _data_files: log.error( 'ignoring duplicate Community/ContextName "%s" for data ' "file %s (%s already loaded)" % (community_name, full_path, _data_files[community_name]) ) continue elif full_path in _mib_instrums: mib_instrum = _mib_instrums[full_path] log.info(f"Configuring *shared* {mib_instrum}") else: data_file = datafile.DataFile( full_path, text_parser, variation_modules ) data_file.index_text(args.force_index_rebuild, args.validate_data) MibController = controller.MIB_CONTROLLERS[data_file.layout] mib_instrum = MibController(data_file) _mib_instrums[full_path] = mib_instrum _data_files[community_name] = full_path log.info(f"Configuring {mib_instrum}") log.info(f"SNMPv1/2c community name: {community_name}") contexts[univ.OctetString(community_name)] = mib_instrum data_index_instrum_controller.add_data_file(full_path, community_name) log.msg.dec_ident() del _mib_instrums del _data_files def get_bulk_handler(req_var_binds, non_repeaters, max_repetitions, read_next_vars): """Only v2c arch GETBULK handler""" N = min(int(non_repeaters), len(req_var_binds)) M = int(max_repetitions) R = max(len(req_var_binds) - N, 0) if R: M = min(M, int(args.max_var_binds / R)) if N: rsp_var_binds = read_next_vars(req_var_binds[:N]) else: rsp_var_binds = [] var_binds = req_var_binds[-R:] while M and R: rsp_var_binds.extend(read_next_vars(var_binds)) var_binds = rsp_var_binds[-R:] M -= 1 return rsp_var_binds def commandResponderCbFun( transport_dispatcher, transport_domain, transport_address, whole_msg ): """v2c arch command responder request handling callback""" while whole_msg: msg_ver = api.decodeMessageVersion(whole_msg) if msg_ver in api.protoModules: p_mod = api.protoModules[msg_ver] else: log.error(f"Unsupported SNMP version {msg_ver}") return req_msg, whole_msg = decoder.decode(whole_msg, asn1Spec=p_mod.Message()) community_name = req_msg.getComponentByPosition(1) for candidate in datafile.probe_context( transport_domain, transport_address, context_engine_id=datafile.SELF_LABEL, context_name=community_name, ): if candidate in contexts: log.info( "Using %s selected by candidate %s; transport ID %s, " "source address %s, context engine ID , " "community name " '"%s"' % ( contexts[candidate], candidate, univ.ObjectIdentifier(transport_domain), transport_address[0], community_name, ) ) community_name = candidate break else: log.error( "No data file selected for transport ID %s, source " "address %s, community name " '"%s"' % ( univ.ObjectIdentifier(transport_domain), transport_address[0], community_name, ) ) return whole_msg rsp_msg = p_mod.apiMessage.get_response(req_msg) rsp_pdu = p_mod.apiMessage.get_pdu(rsp_msg) req_pdu = p_mod.apiMessage.get_pdu(req_msg) if req_pdu.isSameTypeWith(p_mod.GetRequestPDU()): backend_fun = contexts[community_name].readVars elif req_pdu.isSameTypeWith(p_mod.SetRequestPDU()): backend_fun = contexts[community_name].writeVars elif req_pdu.isSameTypeWith(p_mod.GetNextRequestPDU()): backend_fun = contexts[community_name].readNextVars elif hasattr(p_mod, "GetBulkRequestPDU") and req_pdu.isSameTypeWith( p_mod.GetBulkRequestPDU() ): if not msg_ver: log.info( "GETBULK over SNMPv1 from %s:%s" % (transport_domain, transport_address) ) return whole_msg def backend_fun(var_binds): return get_bulk_handler( var_binds, p_mod.apiBulkPDU.get_non_repeaters(req_pdu), p_mod.apiBulkPDU.get_max_repetitions(req_pdu), contexts[community_name].readNextVars, ) else: log.error( "Unsupported PDU type %s from " "%s:%s" % (req_pdu.__class__.__name__, transport_domain, transport_address) ) return whole_msg try: var_binds = backend_fun(p_mod.apiPDU.get_varbinds(req_pdu)) except NoDataNotification: return whole_msg except Exception as exc: log.error("Ignoring SNMP engine failure: %s" % exc) return whole_msg if not msg_ver: for idx in range(len(var_binds)): oid, val = var_binds[idx] if val.tagSet in SNMP_2TO1_ERROR_MAP: var_binds = p_mod.apiPDU.get_varbinds(req_pdu) p_mod.apiPDU.setErrorStatus( rsp_pdu, SNMP_2TO1_ERROR_MAP[val.tagSet] ) p_mod.apiPDU.setErrorIndex(rsp_pdu, idx + 1) break p_mod.apiPDU.set_varbinds(rsp_pdu, var_binds) transport_dispatcher.send_message( encoder.encode(rsp_msg), transport_domain, transport_address ) return whole_msg # Configure access to data index log.info( "Maximum number of variable bindings in SNMP response: %s" % args.max_var_binds ) data_index_instrum_controller = controller.DataIndexInstrumController() contexts = {univ.OctetString("index"): data_index_instrum_controller} with daemon.PrivilegesOf(args.process_user, args.process_group): configure_managed_objects( args.data_dirs or confdir.data, data_index_instrum_controller ) contexts["index"] = data_index_instrum_controller # Configure socket server transport_dispatcher = AsyncioDispatcher() transport_index = args.transport_id_offset for agent_udpv4_endpoint in args.agent_udpv4_endpoints: transport_domain = udp.domainName + (transport_index,) transport_index += 1 agent_udpv4_endpoint = endpoints.IPv4TransportEndpoints().add( agent_udpv4_endpoint ) transport_dispatcher.registerTransport( transport_domain, agent_udpv4_endpoint[0] ) log.msg( "Listening at UDP/IPv4 endpoint %s, transport ID " "%s" % ( agent_udpv4_endpoint[1], ".".join([str(handler) for handler in transport_domain]), ) ) transport_index = args.transport_id_offset for agent_udpv6_endpoint in args.agent_udpv6_endpoints: transport_domain = udp6.domainName + (transport_index,) transport_index += 1 agent_udpv6_endpoint = endpoints.IPv4TransportEndpoints().add( agent_udpv6_endpoint ) transport_dispatcher.registerTransport( transport_domain, agent_udpv6_endpoint[0] ) log.msg( "Listening at UDP/IPv6 endpoint %s, transport ID " "%s" % ( agent_udpv6_endpoint[1], ".".join([str(handler) for handler in transport_domain]), ) ) transport_dispatcher.registerRecvCbFun(commandResponderCbFun) transport_dispatcher.jobStarted(1) # server job would never finish with daemon.PrivilegesOf(args.process_user, args.process_group, final=True): try: transport_dispatcher.runDispatcher() except KeyboardInterrupt: log.info("Shutting down process...") finally: if variation_modules: log.info("Shutting down variation modules:") for name, contexts in variation_modules.items(): body = contexts[0] try: body["shutdown"](options=body["args"], mode="variation") except Exception as exc: log.error( 'Variation module "%s" shutdown FAILED: %s' % (name, exc) ) else: log.info('Variation module "%s" shutdown OK' % name) transport_dispatcher.closeDispatcher() log.info("Process terminated") return 0 if __name__ == "__main__": try: rc = main() except KeyboardInterrupt: sys.stderr.write("shutting down process...") rc = 0 except Exception as exc: sys.stderr.write("process terminated: %s" % exc) for line in traceback.format_exception(*sys.exc_info()): sys.stderr.write(line.replace("\n", ";")) rc = 1 sys.exit(rc) snmpsim-1.2.2/snmpsim/data/README.txt0000644000000000000000000000054213615410400014220 0ustar00This directory is a repository of .snmprec files -- those are picked by Simulator using community name (SNMPv1/v2c) or context name (SNMPv3) as a selector. The contents of .snmprec files is used by Simulator for building responses. Simulator will report .snmprec files it finds on startup along with community/context names to be used to address them. snmpsim-1.2.2/snmpsim/data/public.snmprec0000644000000000000000000062456413615410400015411 0ustar001.3.6.1.2.1.1.1.0|4|Linux zeus 4.8.6.5-smp #2 SMP Sun Nov 13 14:58:11 CDT 2016 i686 1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10 1.3.6.1.2.1.1.3.0|67:numeric|rate=100,initial=123999999 1.3.6.1.2.1.1.4.0|4|LeXtudio Inc., support@lextudio.com 1.3.6.1.2.1.1.5.0|4:writecache|value=zeus.pysnmp.com (you can change this!) 1.3.6.1.2.1.1.6.0|4|Toronto, Ontario, Canada 1.3.6.1.2.1.1.7.0|2|72 1.3.6.1.2.1.1.8.0|67:numeric|rate=100,initial=123999999 1.3.6.1.2.1.1.9.1.2.1|6|1.3.6.1.6.3.10.3.1.1 1.3.6.1.2.1.1.9.1.2.2|6|1.3.6.1.6.3.11.3.1.1 1.3.6.1.2.1.1.9.1.2.3|6|1.3.6.1.6.3.15.2.1.1 1.3.6.1.2.1.1.9.1.2.4|6|1.3.6.1.6.3.1 1.3.6.1.2.1.1.9.1.2.5|6|1.3.6.1.2.1.49 1.3.6.1.2.1.1.9.1.2.6|6|1.3.6.1.2.1.4 1.3.6.1.2.1.1.9.1.2.7|6|1.3.6.1.2.1.50 1.3.6.1.2.1.1.9.1.2.8|6|1.3.6.1.6.3.16.2.2.1 1.3.6.1.2.1.1.9.1.3.1|4|The SNMP Management Architecture MIB. 1.3.6.1.2.1.1.9.1.3.2|4|The MIB for Message Processing and Dispatching. 1.3.6.1.2.1.1.9.1.3.3|4|The management information definitions for the SNMP User-based Security Model. 1.3.6.1.2.1.1.9.1.3.4|4|The MIB module for SNMPv2 entities 1.3.6.1.2.1.1.9.1.3.5|4|The MIB module for managing TCP implementations 1.3.6.1.2.1.1.9.1.3.6|4|The MIB module for managing IP and ICMP implementations 1.3.6.1.2.1.1.9.1.3.7|4|The MIB module for managing UDP implementations 1.3.6.1.2.1.1.9.1.3.8|4|View-based Access Control Model for SNMP. 1.3.6.1.2.1.1.9.1.4.1|67:numeric|rate=0.001 1.3.6.1.2.1.1.9.1.4.2|67:numeric|rate=0.001 1.3.6.1.2.1.1.9.1.4.3|67:numeric|rate=0.001 1.3.6.1.2.1.1.9.1.4.4|67:numeric|rate=0.001 1.3.6.1.2.1.1.9.1.4.5|67:numeric|rate=0.001 1.3.6.1.2.1.1.9.1.4.6|67:numeric|rate=0.001 1.3.6.1.2.1.1.9.1.4.7|67:numeric|rate=0.001 1.3.6.1.2.1.1.9.1.4.8|67:numeric|rate=0.001 1.3.6.1.2.1.2.1.0|2|2 1.3.6.1.2.1.2.2.1.1.1|2|1 1.3.6.1.2.1.2.2.1.1.2|2|2 1.3.6.1.2.1.2.2.1.2.1|4|lo 1.3.6.1.2.1.2.2.1.2.2|4|eth0 1.3.6.1.2.1.2.2.1.3.1|2|24 1.3.6.1.2.1.2.2.1.3.2|2|6 1.3.6.1.2.1.2.2.1.4.1|2|16436 1.3.6.1.2.1.2.2.1.4.2|2|1500 1.3.6.1.2.1.2.2.1.5.1|66|10000000 1.3.6.1.2.1.2.2.1.5.2|66|100000000 1.3.6.1.2.1.2.2.1.6.1|4| 1.3.6.1.2.1.2.2.1.6.2|4x|00127962f940 1.3.6.1.2.1.2.2.1.7.1|2|1 1.3.6.1.2.1.2.2.1.7.2|2|1 1.3.6.1.2.1.2.2.1.8.1|2|1 1.3.6.1.2.1.2.2.1.8.2|2|1 1.3.6.1.2.1.2.2.1.9.1|67:numeric|rate=0.0001,initial=123999999 1.3.6.1.2.1.2.2.1.9.2|67:numeric|rate=0.0001,initial=123999999 1.3.6.1.2.1.2.2.1.10.1|65:numeric|rate=200,deviation=10,cumulative=1 1.3.6.1.2.1.2.2.1.10.2|65:numeric|rate=300,deviation=10,cumulative=1 1.3.6.1.2.1.2.2.1.11.1|65:numeric|rate=18,initial=1,cumulative=1 1.3.6.1.2.1.2.2.1.11.2|65:numeric|rate=38,initial=1,cumulative=1 1.3.6.1.2.1.2.2.1.12.1|65:numeric|rate=1,cumulative=1 1.3.6.1.2.1.2.2.1.12.2|65:numeric|rate=2,cumulative=1 1.3.6.1.2.1.2.2.1.13.1|65:numeric|scale=0.4,offset=1,deviation=1,function=cos,cumulative=1 1.3.6.1.2.1.2.2.1.13.2|65:numeric|scale=0.6,offset=1,deviation=1,function=cos,cumulative=1 1.3.6.1.2.1.2.2.1.14.1|65:numeric|scale=0.1,offset=1,deviation=1,function=sin,cumulative=1 1.3.6.1.2.1.2.2.1.14.2|65:numeric|scale=0.2,offset=1,deviation=1,function=sin,cumulative=1 1.3.6.1.2.1.2.2.1.15.1|65:numeric|rate=0.1,deviation=2,cumulative=1 1.3.6.1.2.1.2.2.1.15.2|65:numeric|rate=0.1,deviation=1,cumulative=1 1.3.6.1.2.1.2.2.1.16.1|65:numeric|rate=90,deviation=3,cumulative=1 1.3.6.1.2.1.2.2.1.16.2|65:numeric|rate=100,deviation=6,cumulative=1 1.3.6.1.2.1.2.2.1.17.1|65:numeric|rate=5,initial=1,cumulative=1 1.3.6.1.2.1.2.2.1.17.2|65:numeric|rate=9,initial=1,cumulative=1 1.3.6.1.2.1.2.2.1.18.1|65:numeric|rate=1,deviation=10,cumulative=1 1.3.6.1.2.1.2.2.1.18.2|65:numeric|rate=1,deviation=10,cumulative=1 1.3.6.1.2.1.2.2.1.19.1|65:numeric|scale=0.2,offset=1,deviation=1,function=sin,cumulative=1 1.3.6.1.2.1.2.2.1.19.2|65:numeric|scale=0.6,offset=1,deviation=1,function=sin,cumulative=1 1.3.6.1.2.1.2.2.1.20.1|65:numeric|scale=0.1,offset=1,deviation=1,function=cos,cumulative=1 1.3.6.1.2.1.2.2.1.20.2|65:numeric|scale=0.6,offset=1,deviation=1,function=cos,cumulative=1 1.3.6.1.2.1.2.2.1.21.1|66:numeric|function=pow%