/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.snmp.internal; import java.io.IOException; import java.util.Dictionary; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.openhab.binding.snmp.SnmpBindingProvider; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.items.Item; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.transform.TransformationException; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.CommandResponder; import org.snmp4j.CommandResponderEvent; import org.snmp4j.CommunityTarget; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.event.ResponseEvent; import org.snmp4j.event.ResponseListener; import org.snmp4j.security.Priv3DES; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.smi.Address; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.Variable; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; /** * The SNMP binding listens to SNMP Traps on the configured port and posts new * events of type ({@link StringType} to the event bus. * * @author Thomas.Eichstaedt-Engelen * @author Chris Jackson - modified binding to support polling SNMP OIDs (SNMP GET) and setting values (SNMP SET). * @author Jan N. Klug - modified binding to change protocol version * @since 0.9.0 */ public class SnmpBinding extends AbstractActiveBinding<SnmpBindingProvider> implements ManagedService, CommandResponder, ResponseListener { private Snmp snmp; private static final Logger logger = LoggerFactory.getLogger(SnmpBinding.class); private static DefaultUdpTransportMapping transport; private static final int SNMP_DEFAULT_PORT = 162; /** The local port to bind on and listen to SNMP Traps */ private static int port = SNMP_DEFAULT_PORT; /** The SNMP community to filter SNMP Traps */ private static String community; private static int timeout = 1500; private static int retries = 0; /** * the interval to find new refresh candidates (defaults to 1000 * milliseconds) */ private int granularity = 1000; private Map<String, Long> lastUpdateMap = new HashMap<String, Long>(); @Override public void activate() { logger.debug("SNMP binding activated"); super.activate(); } @Override public void deactivate() { stopListening(); logger.debug("SNMP binding deactivated"); } /** * @{inheritDoc */ @Override protected long getRefreshInterval() { return granularity; } /** * @{inheritDoc */ @Override protected String getName() { return "SNMP Refresh Service"; } /** * Configures a {@link DefaultUdpTransportMapping} and starts listening on * <code>SnmpBinding.port</code> for incoming SNMP Traps. */ private void listen() { UdpAddress address = new UdpAddress(SnmpBinding.port); try { if (transport != null) { transport.close(); transport = null; } if (snmp != null) { snmp.close(); snmp = null; } transport = new DefaultUdpTransportMapping(address); // add all security protocols SecurityProtocols.getInstance().addDefaultProtocols(); SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES()); // Create Target if (SnmpBinding.community != null) { CommunityTarget target = new CommunityTarget(); target.setCommunity(new OctetString(SnmpBinding.community)); } snmp = new Snmp(transport); transport.listen(); logger.debug("SNMP binding is listening on " + address); } catch (IOException ioe) { logger.error("SNMP binding couldn't listen to " + address, ioe); } } /** * Stops listening for incoming SNMP Traps */ private void stopListening() { if (transport != null) { try { transport.close(); } catch (IOException ioe) { logger.error("couldn't close connection", ioe); } transport = null; } if (snmp != null) { try { snmp.close(); } catch (IOException ioe) { logger.error("couldn't close snmp", ioe); } snmp = null; } } /** * Will be called whenever a {@link PDU} is received on the given port * specified in the listen() method. It extracts a {@link Variable} * according to the configured OID prefix and sends its value to the event * bus. */ @Override public void processPdu(CommandResponderEvent event) { Address addr = event.getPeerAddress(); if (addr == null) { return; } String s = addr.toString().split("/")[0]; if (s == null) { logger.error("TRAP: failed to translate address {}", addr); dispatchPdu(addr, event.getPDU()); } else { // Need to change the port to 161, which is what the bindings are configured for since // at least some SNMP devices send traps from a random port number. Otherwise the trap // won't be found as the address check will fail. It feels like there should be a better // way to do this!!! Address address = GenericAddress.parse("udp:" + s + "/161"); dispatchPdu(address, event.getPDU()); } } /** * Called when a response from a GET is received * * @see org.snmp4j.event.ResponseListener#onResponse(org.snmp4j.event.ResponseEvent ) */ @Override public void onResponse(ResponseEvent event) { dispatchPdu(event.getPeerAddress(), event.getResponse()); } private void dispatchPdu(Address address, PDU pdu) { if (pdu != null & address != null) { logger.debug("Received PDU from '{}' '{}'", address, pdu); for (SnmpBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { // Check the IP address if (!provider.getAddress(itemName).equals(address)) { continue; } // Check the OID OID oid = provider.getOID(itemName); Variable variable = pdu.getVariable(oid); if (variable != null) { Class<? extends Item> itemType = provider.getItemType(itemName); // Do any transformations String value = variable.toString(); try { value = provider.doTransformation(itemName, value); } catch (TransformationException e) { logger.error("Transformation error with item {}: {}", itemName, e); } // Change to a state State state = null; if (itemType.isAssignableFrom(StringItem.class)) { state = StringType.valueOf(value); } else if (itemType.isAssignableFrom(NumberItem.class)) { state = DecimalType.valueOf(value); } else if (itemType.isAssignableFrom(SwitchItem.class)) { state = OnOffType.valueOf(value); } if (state != null) { eventPublisher.postUpdate(itemName, state); } else { logger.debug("'{}' couldn't be parsed to a State. Valid State-Types are String and Number", variable.toString()); } } else { logger.trace("PDU doesn't contain a variable with OID '{}'", oid.toString()); } } } } } /** * @{inheritDoc */ @Override public void internalReceiveCommand(String itemName, Command command) { logger.debug("SNMP receive command {} from {}", itemName, command); SnmpBindingProvider providerCmd = null; for (SnmpBindingProvider provider : this.providers) { OID oid = provider.getOID(itemName, command); if (oid != null) { providerCmd = provider; break; } } if (providerCmd == null) { logger.warn("No match for binding provider [itemName={}, command={}]", itemName, command); return; } logger.debug("SNMP command for {} to {}", itemName, providerCmd.toString()); // Set up the target CommunityTarget target = new CommunityTarget(); target.setCommunity(providerCmd.getCommunity(itemName, command)); target.setAddress(providerCmd.getAddress(itemName, command)); target.setRetries(retries); target.setTimeout(timeout); target.setVersion(providerCmd.getSnmpVersion(itemName, command)); Variable var = providerCmd.getValue(itemName, command); OID oid = providerCmd.getOID(itemName, command); VariableBinding varBind = new VariableBinding(oid, var); // Create the PDU PDU pdu = new PDU(); pdu.add(varBind); pdu.setType(PDU.SET); pdu.setRequestID(new Integer32(1)); logger.debug("SNMP: Send CMD PDU {} {}", providerCmd.getAddress(itemName, command), pdu); if (snmp == null) { logger.error("SNMP: snmp not initialised - aborting request"); } else { sendPDU(target, pdu); } } /** * @{inheritDoc */ @Override public void execute() { for (SnmpBindingProvider provider : providers) { for (String itemName : provider.getInBindingItemNames()) { int refreshInterval = provider.getRefreshInterval(itemName); Long lastUpdateTimeStamp = lastUpdateMap.get(itemName); if (lastUpdateTimeStamp == null) { lastUpdateTimeStamp = 0L; } long age = System.currentTimeMillis() - lastUpdateTimeStamp; boolean needsUpdate; if (refreshInterval == 0) { needsUpdate = false; } else { needsUpdate = age >= refreshInterval; } if (needsUpdate) { logger.debug("Item '{}' is about to be refreshed", itemName); // Set up the target CommunityTarget target = new CommunityTarget(); target.setCommunity(provider.getCommunity(itemName)); target.setAddress(provider.getAddress(itemName)); target.setRetries(retries); target.setTimeout(timeout); target.setVersion(provider.getSnmpVersion(itemName)); // Create the PDU PDU pdu = new PDU(); pdu.add(new VariableBinding(provider.getOID(itemName))); pdu.setType(PDU.GET); logger.debug("SNMP: Send PDU {} {}", provider.getAddress(itemName), pdu); if (snmp == null) { logger.error("SNMP: snmp not initialised - aborting request"); } else { sendPDU(target, pdu); } lastUpdateMap.put(itemName, System.currentTimeMillis()); } } } } protected void addBindingProvider(SnmpBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(SnmpBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * {@inheritDoc} */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { boolean mapping = false; stopListening(); if (config != null) { mapping = true; SnmpBinding.community = (String) config.get("community"); if (StringUtils.isBlank(SnmpBinding.community)) { SnmpBinding.community = "public"; logger.info("didn't find SNMP community configuration -> listen to SNMP community {}", SnmpBinding.community); } String portString = (String) config.get("port"); if (StringUtils.isNotBlank(portString) && portString.matches("\\d*")) { SnmpBinding.port = Integer.valueOf(portString).intValue(); } else { SnmpBinding.port = SNMP_DEFAULT_PORT; logger.info( "Didn't find SNMP port configuration or configuration is invalid -> listen to SNMP default port {}", SnmpBinding.port); } String timeoutString = (String) config.get("timeout"); if (StringUtils.isNotBlank(timeoutString)) { SnmpBinding.timeout = Integer.valueOf(timeoutString).intValue(); if (SnmpBinding.timeout < 0 | SnmpBinding.retries > 5) { logger.info("SNMP timeout value is invalid (" + SnmpBinding.timeout + "). Using default value."); SnmpBinding.timeout = 1500; } } else { SnmpBinding.timeout = 1500; logger.info("Didn't find SNMP timeout or configuration is invalid -> timeout set to {}", SnmpBinding.timeout); } String retriesString = (String) config.get("retries"); if (StringUtils.isNotBlank(retriesString)) { SnmpBinding.retries = Integer.valueOf(retriesString).intValue(); if (SnmpBinding.retries < 0 | SnmpBinding.retries > 5) { logger.info("SNMP retries value is invalid (" + SnmpBinding.retries + "). Using default value."); SnmpBinding.retries = 0; } } else { SnmpBinding.retries = 0; logger.info("Didn't find SNMP retries or configuration is invalid -> retries set to {}", SnmpBinding.retries); } } for (SnmpBindingProvider provider : providers) { if (provider.getInBindingItemNames() != null) { mapping = true; } } // Did we find either a trap request, or any bindings if (mapping) { listen(); } setProperlyConfigured(true); } private void sendPDU(CommunityTarget target, PDU pdu) { try { snmp.send(pdu, target, null, this); } catch (IOException e) { logger.error("Error sending PDU", e); } } }