/*
* Copyright 2011 Future Systems
*
* 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.
*/
package org.krakenapps.snmp.impl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Validate;
import org.krakenapps.snmp.SnmpTrap;
import org.krakenapps.snmp.SnmpTrapBinding;
import org.krakenapps.snmp.SnmpTrapReceiver;
import org.krakenapps.snmp.SnmpTrapService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.MessageDispatcher;
import org.snmp4j.MessageDispatcherImpl;
import org.snmp4j.PDU;
import org.snmp4j.PDUv1;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.SMIConstants;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;
@Component(name = "snmp-trap-service")
@Provides
public class SnmpTrapServiceImpl implements SnmpTrapService, CommandResponder {
private final Logger logger = LoggerFactory.getLogger(SnmpTrapServiceImpl.class.getName());
private final Charset charset = Charset.forName("utf-8");
private ConcurrentMap<String, SnmpTrapBinding> bindings;
private ConcurrentMap<String, SnmpListener> listeners;
private CopyOnWriteArraySet<SnmpTrapReceiver> callbacks;
private ConcurrentMap<String, CopyOnWriteArraySet<SnmpTrapReceiver>> bindingCallbacks;
public SnmpTrapServiceImpl() {
bindings = new ConcurrentHashMap<String, SnmpTrapBinding>();
listeners = new ConcurrentHashMap<String, SnmpListener>();
callbacks = new CopyOnWriteArraySet<SnmpTrapReceiver>();
bindingCallbacks = new ConcurrentHashMap<String, CopyOnWriteArraySet<SnmpTrapReceiver>>();
}
@Validate
public void start() {
}
@Invalidate
public void stop() {
// close all listeners
for (SnmpListener listener : listeners.values()) {
try {
listener.snmp.close();
listener.threadPool.stop();
} catch (IOException e) {
logger.error("kraken snmp: close failed", e);
}
}
listeners.clear();
bindings.clear();
callbacks.clear();
bindingCallbacks.clear();
}
@Override
public List<String> getBindingNames() {
return new ArrayList<String>(bindings.keySet());
}
@Override
public SnmpTrapBinding getBinding(String name) {
return bindings.get(name);
}
@Override
public SnmpTrapBinding getBinding(InetSocketAddress listenAddress) {
for (SnmpTrapBinding b : bindings.values())
if (b.getListenAddress().equals(listenAddress))
return b;
return null;
}
@Override
public void open(SnmpTrapBinding binding) throws IOException {
if (binding.getName() == null || binding.getName().isEmpty())
throw new IllegalArgumentException("empty binding name");
SnmpTrapBinding old = bindings.putIfAbsent(binding.getName(), binding);
if (old != null)
throw new IllegalStateException("duplicated binding name: " + binding.getName());
String addr = binding.getListenAddress().getAddress().getHostAddress();
int port = binding.getListenAddress().getPort();
UdpAddress udpAddress = new UdpAddress(addr + "/" + port);
try {
String threadPoolName = "SNMP Trap [" + binding.getName() + "]";
ThreadPool threadPool = ThreadPool.create(threadPoolName, binding.getThreadCount());
MessageDispatcher dispatcher = new MultiThreadedMessageDispatcher(threadPool, new MessageDispatcherImpl());
TransportMapping transport = new DefaultUdpTransportMapping(udpAddress);
Snmp snmp = new Snmp(dispatcher, transport);
snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
snmp.addCommandResponder(this);
snmp.listen();
listeners.put(binding.getName(), new SnmpListener(snmp, threadPool));
logger.info("kraken snmp: opened {} [{}]", binding.getName(), binding.getListenAddress());
} catch (IOException e) {
bindings.remove(binding.getName());
throw e;
}
}
@Override
public void close(String name) throws IOException {
SnmpListener listener = listeners.remove(name);
if (listener != null) {
listener.snmp.close();
listener.threadPool.stop();
}
SnmpTrapBinding binding = bindings.remove(name);
if (binding == null)
throw new IllegalStateException("snmp trap binding not found: " + name);
logger.info("kraken snmp: closed {} [{}]", binding.getName(), binding.getListenAddress());
}
@Override
public void addReceiver(SnmpTrapReceiver callback) {
callbacks.add(callback);
}
@Override
public void removeReceiver(SnmpTrapReceiver callback) {
callbacks.remove(callback);
}
@Override
public void addReceiver(String name, SnmpTrapReceiver callback) {
CopyOnWriteArraySet<SnmpTrapReceiver> set = new CopyOnWriteArraySet<SnmpTrapReceiver>();
CopyOnWriteArraySet<SnmpTrapReceiver> old = bindingCallbacks.putIfAbsent(name, set);
if (old != null)
set = old;
set.add(callback);
}
@Override
public void removeReceiver(String name, SnmpTrapReceiver callback) {
CopyOnWriteArraySet<SnmpTrapReceiver> set = new CopyOnWriteArraySet<SnmpTrapReceiver>();
CopyOnWriteArraySet<SnmpTrapReceiver> old = bindingCallbacks.putIfAbsent(name, set);
if (old != null)
set = old;
set.remove(callback);
}
public void processPdu(CommandResponderEvent e) {
PDU command = e.getPDU();
if (command == null)
return;
if (logger.isTraceEnabled())
logger.trace("kraken snmp: trap [{}]", e.toString());
InetSocketAddress remote = toInetSocketAddress(e.getPeerAddress());
InetSocketAddress local = toInetSocketAddress(e.getTransportMapping().getListenAddress());
SnmpTrap trap = new SnmpTrap();
trap.setRemoteAddress(remote);
trap.setLocalAddress(local);
if (command instanceof PDUv1) {
PDUv1 v1 = (PDUv1) command;
trap.setEnterpriseOid(v1.getEnterprise().toString());
trap.setGenericTrap(v1.getGenericTrap());
trap.setSpecificTrap(v1.getSpecificTrap());
}
for (Object o : command.getVariableBindings()) {
VariableBinding binding = (VariableBinding) o;
String oid = binding.getOid().toString();
Object value = toPrimitive(binding.getVariable());
trap.getVariableBindings().put(oid, value);
}
// invoke callbacks
SnmpTrapBinding binding = getBinding(local);
if (binding != null) {
CopyOnWriteArraySet<SnmpTrapReceiver> callbacks = bindingCallbacks.get(binding.getName());
if (callbacks != null)
dispatchCallbacks(trap, callbacks);
}
dispatchCallbacks(trap, callbacks);
}
private void dispatchCallbacks(SnmpTrap trap, CopyOnWriteArraySet<SnmpTrapReceiver> callbacks) {
for (SnmpTrapReceiver callback : callbacks) {
try {
callback.handle(trap);
} catch (Throwable t) {
logger.warn("kraken snmp: callback should not throw any exception", t);
}
}
}
private InetSocketAddress toInetSocketAddress(Address address) {
try {
String[] tokens = address.toString().split("/");
int port = Integer.parseInt(tokens[1]);
InetAddress addr = InetAddress.getByName(tokens[0]);
return new InetSocketAddress(addr, port);
} catch (UnknownHostException e) {
return null;
}
}
private Object toPrimitive(Variable var) {
switch (var.getSyntax()) {
case SMIConstants.SYNTAX_COUNTER32:
return var.toInt();
case SMIConstants.SYNTAX_COUNTER64:
return var.toLong();
case SMIConstants.SYNTAX_GAUGE32:
return var.toLong();
case SMIConstants.SYNTAX_INTEGER:
return var.toInt();
case SMIConstants.SYNTAX_IPADDRESS:
try {
return InetAddress.getByName(var.toString());
} catch (UnknownHostException e) {
return null;
}
case SMIConstants.SYNTAX_NULL:
return null;
case SMIConstants.SYNTAX_OBJECT_IDENTIFIER:
return var.toString();
case SMIConstants.SYNTAX_OCTET_STRING:
return new String(((OctetString) var).getValue(), charset);
case SMIConstants.SYNTAX_TIMETICKS:
return null;
default:
return null;
}
}
private class SnmpListener {
private Snmp snmp;
private ThreadPool threadPool;
public SnmpListener(Snmp snmp, ThreadPool threadPool) {
this.snmp = snmp;
this.threadPool = threadPool;
}
}
}