Signed-off-by: Isaku Yamahata <[email protected]> --- Changes v1 -> v2: - use RYU-MIB - populate datapath table on datapath enter/leave event --- ryu/services/snmp/agent.py | 286 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 ryu/services/snmp/agent.py
diff --git a/ryu/services/snmp/agent.py b/ryu/services/snmp/agent.py new file mode 100644 index 0000000..4244b1b --- /dev/null +++ b/ryu/services/snmp/agent.py @@ -0,0 +1,286 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 Isaku Yamahata <yamahata at private email ne jp> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# to watch traps +# snmptrapd -C -Lo -f --authCommunity='log traps' \ +# --createUser='-e 0x8000000001020304 ryu-snmp-user MD5 authkey1 DES privkey1'\ +# --authuser='log ryu-snmp-user authPriv' +# +# to get informations +# snmpwalk -v 3 -u ryu-snmp-user -e 0x8000000001020304 \ +# -l authPriv -a MD5 -A authkey1 -x DES -X privkey1 127.0.0.1 enterprise + +import os.path + +from oslo.config import cfg +from pyasn1.compat.octets import null +from pysnmp.entity import config +from pysnmp.entity import engine +from pysnmp.entity.rfc3413 import cmdrsp +from pysnmp.entity.rfc3413 import context +from pysnmp.entity.rfc3413 import ntforg +from pysnmp.entity.rfc3413.oneliner import mibvar +from pysnmp.carrier.asynsock.dgram import udp +from pysnmp.proto.api import v2c +from pysnmp.smi import builder +from pysnmp.smi import view + + +from ryu.base import app_manager +from ryu.controller import handler +from ryu.lib import dpid as dpid_lib +from ryu.lib import hub +from ryu.topology import event as topo_event + + +CONF = cfg.CONF +CONF.register_opts([ + cfg.StrOpt('snmp-host', default='', + help='snmp IP address to get request'), + cfg.IntOpt('snmp-port', default=161, + help='snmp port to get request'), + cfg.StrOpt('snmp-target-host', default='127.0.0.1', + help='snmptrap target host ip address to send trap'), + cfg.IntOpt('snmp-target-port', default=162, + help='snmptrap port to send trap'), + + cfg.StrOpt('snmp-notification-target', default='ryu-notification', + help='snmptrap target name'), + + cfg.MultiStrOpt('mib-dir', default=[], + help='directories to load MIBs'), +]) + + +from pysnmp import debug +debug.setLogger(debug.Debug('all')) + + +class SNMPAgent(app_manager.RyuApp): + _DEFAULT_ENGINE_ID = '8000000001020304' + _DEFAULT_V3_USER = 'ryu-snmp-user' + _DEFAULT_SECURITY_NAME = 'ryu-creds' + _DEFAULT_NMS = 'ryu-nms' + # 'noAuthNoPriv', 'authNoPrive', 'authPriv' + _DEFAULT_SECURITY_LEVEL = 'authPriv' + _DEFAULT_AUTH = config.usmHMACMD5AuthProtocol # 'SHA', 'MD5' + _DEFAULT_AUTH_KEY = 'authkey1' + _DEFAULT_PRIV = config.usmDESPrivProtocol # 'DES', 'AES' + _DEFAULT_PRIV_KEY = 'privkey1' + + _NOTIFICATION_NAME = 'ryu-notification' + _PARAMS_NAME = 'my-filter' + _TRANSPORT_TAG = 'all-my-managers' + + def __init__(self, *args, **kwargs): + super(SNMPAgent, self).__init__(*args, **kwargs) + snmp_engine = engine.SnmpEngine( + v2c.OctetString(hexValue=self._DEFAULT_ENGINE_ID)) + self._snmp_engine = snmp_engine + + # TODO authProtocol, authKey, privProtocol, privKey, contextEngineId + config.addV3User(snmp_engine, self._DEFAULT_V3_USER, + self._DEFAULT_AUTH, self._DEFAULT_AUTH_KEY, + self._DEFAULT_PRIV, self._DEFAULT_PRIV_KEY) + config.addTargetParams(snmp_engine, + self._DEFAULT_SECURITY_NAME, + self._DEFAULT_V3_USER, + self._DEFAULT_SECURITY_LEVEL) + udp_domain_name_target = udp.domainName + (1, ) + config.addSocketTransport( + snmp_engine, udp_domain_name_target, + udp.UdpTransport().openClientMode()) + config.addTargetAddr(snmp_engine, + self._DEFAULT_NMS, + udp_domain_name_target, + (CONF.snmp_target_host, CONF.snmp_target_port), + self._DEFAULT_SECURITY_NAME, + tagList=self._TRANSPORT_TAG) + config.addNotificationTarget(snmp_engine, + self._NOTIFICATION_NAME, + self._PARAMS_NAME, + self._TRANSPORT_TAG, + 'trap') + config.addContext(snmp_engine, '') + # Allow NOTIFY access to Agent's MIB by this SNMP model + config.addVacmUser(snmp_engine, 3, # SNMPv3 + self._DEFAULT_V3_USER, self._DEFAULT_SECURITY_LEVEL, + (), (), (1, 3, 6)) + snmp_context = context.SnmpContext(snmp_engine) + self._snmp_context = snmp_context + self._ntf_org = ntforg.NotificationOriginator(snmp_context) + + # command responder + config.addSocketTransport( + snmp_engine, udp.domainName + (2, ), + udp.UdpTransport().openServerMode((CONF.snmp_host, + CONF.snmp_port))) + mib_builder = self._snmp_context.getMibInstrum().getMibBuilder() + self._mib_builder = mib_builder + + mib_sources = list(mib_builder.getMibSources()) + dirname = os.path.dirname(__file__) + mib_sources.append(builder.DirMibSource(os.path.join(dirname, 'mibs'))) + mib_sources.append(builder.DirMibSource(os.path.join( + dirname, 'mibs', 'instances'))) + # mib_sources.append(builder.DirMibSource('.')) + mib_sources.extend([builder.DirMibSource(CONF.mib_dir) + for mib_dir in CONF.mib_dir]) + mib_builder.setMibSources(*mib_sources) + + cmdrsp.GetCommandResponder(snmp_engine, snmp_context) + cmdrsp.NextCommandResponder(snmp_engine, snmp_context) + cmdrsp.BulkCommandResponder(snmp_engine, snmp_context) + cmdrsp.SetCommandResponder(snmp_engine, snmp_context) + + # for MIB resolution + mib_view_controller = view.MibViewController(mib_builder) + self._mib_view_controller = mib_view_controller + + # RYU-MIB + mib_builder.loadModules('RYU-MIB') + mib_builder.loadModules('__RYU-MIB') + + # view + sys_name = mibvar.MibVariable('SNMPv2-MIB', 'sysName', 0) + sys_name.resolveWithMib(mib_view_controller) + config.addVacmUser(snmp_engine, 3, self._DEFAULT_V3_USER, + self._DEFAULT_SECURITY_LEVEL, + sys_name) + ryu_mib = mibvar.MibVariable('RYU-MIB', 'ryuMIB') + ryu_mib.resolveWithMib(mib_view_controller) + config.addVacmUser(snmp_engine, 3, self._DEFAULT_V3_USER, + self._DEFAULT_SECURITY_LEVEL, + ryu_mib) + + def start(self): + self.threads.append(hub.spawn(self._loop)) + super(SNMPAgent, self).start() + sys_name = mibvar.MibVariable('SNMPv2-MIB', 'sysName', 0) + sys_name.resolveWithMib(self._mib_view_controller) + self.send_trap(None, ('SNMPv2-MIB', 'coldStart'), + ((sys_name, v2c.OctetString('Ryu-SNMP-agent')), )) + + def _loop(self): + self._snmp_engine.transportDispatcher.jobStarted(1) + try: + self._snmp_engine.transportDispatcher.runDispatcher() + except: + self._snmp_engine.transportDispatcher.closeDispatcher() + raise + + def send_trap(self, target, name, var_binds=(), cb_fun=None, cb_ctx=None, + context_name=null, instance_index=None): + print var_binds + if target is None: + target = self._NOTIFICATION_NAME + error_indication = self._ntf_org.sendNotification( + self._snmp_engine, target, name, var_binds, cb_fun, cb_ctx, + context_name, instance_index) + + def _refresh_mib(self): + self._snmp_context.getMibInstrum()._MibInstrumController__indexMib() + + @staticmethod + def _dp_instance_symbol(symbol, instance_index): + #return symbol + '_' + ''.join('%02x' % i for i in instance_index) + return symbol + '_' + '_'.join('%d' % i for i in instance_index) + + @handler.set_ev_cls(topo_event.EventSwitchEnter) + def switch_enter_handler(self, ev): + dpid_buf = dpid_lib.dpid_to_buf(ev.switch.dp.id) + mib_builder = self._mib_builder + (MibScalar, + MibScalarInstance) = mib_builder.importSymbols('SNMPv2-SMI', + 'MibScalar', + 'MibScalarInstance') + (datapathEntry, + dpIndex, + dpID, + dpNBuffers, + dpNTables, + dpAuxiliaryID, + dpCapabilities, + dpRowStatus, + datapathConnected) = mib_builder.importSymbols('RYU-MIB', + 'datapathEntry', + 'dpIndex', + 'dpID', + 'dpNBuffers', + 'dpNTables', + 'dpAuxiliaryID', + 'dpCapabilities', + 'dpRowStatus', + 'datapathConnected') + + instance_index = datapathEntry.getInstIdFromIndices(dpid_buf) + dpIndex_ = MibScalarInstance(dpIndex.name, instance_index, + dpIndex.syntax.clone(dpid_buf)) + dpID_ = MibScalarInstance(dpID.name, instance_index, + dpID.syntax.clone(dpid_buf)) + dpNBuffers_ = MibScalarInstance(dpNBuffers.name, instance_index, + dpNBuffers.syntax.clone(0)) + dpNTables_ = MibScalarInstance(dpNTables.name, instance_index, + dpNTables.syntax.clone(0)) + dpAuxiliaryID_ = MibScalarInstance(dpAuxiliaryID.name, instance_index, + dpAuxiliaryID.syntax.clone(0)) + dpCapabilities_ = MibScalarInstance(dpCapabilities.name, + instance_index, + dpCapabilities.syntax.clone(0)) + dpRowStatus_ = MibScalarInstance(dpRowStatus.name, instance_index, + dpRowStatus.syntax.clone()) + + dp_dict = dict((self._dp_instance_symbol(name, instance_index), i) + for name, i in ( + ('dpIndex', dpIndex_), + ('dpID', dpID_), + ('dpNBuffers', dpNBuffers_), + ('dpNTables', dpNTables_), + ('dpAuxiliaryID', dpAuxiliaryID_), + ('dpCapabilities', dpCapabilities_), + ('dpRowStatus', dpRowStatus_))) + mib_builder.exportSymbols('__RYU-MIB', **dp_dict) + + # without this, pysnmp.entity.rfc3413.ntforg.sendNotification() + # fails to find the object. + self._refresh_mib() + + self.send_trap(None, ('RYU-MIB', 'datapathConnected'), + instance_index=instance_index) + + @handler.set_ev_cls(topo_event.EventSwitchLeave) + def switch_leave_handler(self, ev): + dpid_buf = dpid_lib.dpid_to_buf(ev.switch.dp.id) + mib_builder = self._mib_builder + (datapathEntry, + dpID, + datapathDisconnected + ) = mib_builder.importSymbols('RYU-MIB', + 'datapathEntry', + 'dpID', + 'datapathDisconnected') + + instance_index = datapathEntry.getInstIdFromIndices(dpid_buf) + # TODO: check if instance exists. If not, populate first + self.send_trap(None, ('RYU-MIB', 'datapathDisconnected'), + instance_index=instance_index) + + dp_symbols = ('dpIndex', 'dpID', 'dpNBuffers', 'dpNTables', + 'dpAuxiliaryID', 'dpCapabilities', 'dpRowStatus') + dp_symbols = [self._dp_instance_symbol(symbol, instance_index) + for symbol in dp_symbols] + mib_builder.unexportSymbols('__RYU-MIB', *dp_symbols) + # self._refresh_mib() -- 1.7.10.4 ------------------------------------------------------------------------------ AlienVault Unified Security Management (USM) platform delivers complete security visibility with the essential security capabilities. Easily and efficiently configure, manage, and operate all of your security controls from a single console and one unified framework. Download a free trial. http://p.sf.net/sfu/alienvault_d2d _______________________________________________ Ryu-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/ryu-devel
