All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.snmp4j.transport.DefaultTcpTransportMapping Maven / Gradle / Ivy

/*_############################################################################
  _## 
  _##  SNMP4J - DefaultTcpTransportMapping.java  
  _## 
  _##  Copyright (C) 2003-2024  Frank Fock (SNMP4J.org)
  _##  
  _##  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.snmp4j.transport;

import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;

import org.snmp4j.TransportStateReference;
import org.snmp4j.asn1.*;
import org.snmp4j.asn1.BER.*;
import org.snmp4j.log.*;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.smi.*;
import org.snmp4j.SNMP4JSettings;
import org.snmp4j.util.WorkerTask;
import org.snmp4j.util.CommonTimer;

/**
 * The {@code DefaultTcpTransportMapping} implements a TCP transport
 * mapping with the Java 1.4 new IO API.
 * 

* It uses a single thread for processing incoming and outgoing messages. * The thread is started when the {@code listen} method is called, or * when an outgoing request is sent using the {@code sendMessage} method. * * @author Frank Fock * @version 3.0 */ public class DefaultTcpTransportMapping extends TcpTransportMapping { /** * The maximum number of loops trying to read data from an incoming port but no data has been received. * A value of 0 or less disables the check. */ public static final int DEFAULT_MAX_BUSY_LOOPS = 100; private static final LogAdapter logger = LogFactory.getLogger(DefaultTcpTransportMapping.class); protected ServerThread serverThread; private static final int MIN_SNMP_HEADER_LENGTH = 6; protected MessageLengthDecoder messageLengthDecoder = new SnmpMesssageLengthDecoder(); /** * Creates a default TCP transport mapping with the server for incoming * messages disabled. * * @throws IOException * on failure of binding a local port. */ public DefaultTcpTransportMapping() throws IOException { super(new TcpAddress(InetAddress.getLocalHost(), 0)); } /** * Creates a default TCP transport mapping that binds to the given address * (interface) on the local host and enables server mode on request. * * @param serverAddress * the TcpAddress instance that describes the server address to listen * on incoming connection requests. * @param serverEnabled * if {@code true} the server mode is enabled and incoming new connections are accepted. Use {@code false} * to allow outgoing messages and their responses only (client mode). * @throws IOException * if the given address cannot be bound. */ public DefaultTcpTransportMapping(TcpAddress serverAddress, boolean serverEnabled) throws IOException { super(serverAddress); this.serverEnabled = serverEnabled; } /** * Creates a default TCP transport mapping that binds to the given address * (interface) on the local host and enables server mode (i.e. accepts incoming new connections). * * @param serverAddress * the TcpAddress instance that describes the server address to listen * on incoming connection requests. * * @throws IOException * if the given address cannot be bound. */ public DefaultTcpTransportMapping(TcpAddress serverAddress) throws IOException { super(serverAddress); this.serverEnabled = true; } /** * Listen for incoming and outgoing requests. If the {@code serverEnabled} * member is {@code false} the server for incoming requests is not * started. This starts the internal server thread that processes messages. * * @throws SocketException * when the transport is already listening for incoming/outgoing messages. * @throws IOException * if the listen port could not be bound to the server thread. */ public synchronized void listen() throws java.io.IOException { if (getListenWorkerTask() != null) { throw new SocketException("Port already listening"); } serverThread = new ServerThread(); if (logger.isInfoEnabled()) { logger.info("TCP address " + getListenAddress() + " bound successfully"); } listenWorkerTask = SNMP4JSettings.getThreadFactory().createWorkerThread( "DefaultTCPTransportMapping_" + getListenAddress(), serverThread, true); if (getConnectionTimeout() > 0) { // run as daemon socketCleaner = SNMP4JSettings.getTimerFactory().createTimer(); } getListenWorkerTask().run(); } /** * Gets the {@link TransportType} this {@code TransportMapping} supports depending on {@link #isServerEnabled()}. * * @return {@link TransportType#any} if {@link #isServerEnabled()} is {@code true} and * {@link TransportType#sender} otherwise. * @since 3.2.0 */ @Override public TransportType getSupportedTransportType() { return (isServerEnabled() ? TransportType.any : TransportType.sender); } /** * Sends a SNMP message to the supplied address. * * @param address * an {@code TcpAddress}. A {@code ClassCastException} is thrown * if {@code address} is not a {@code TcpAddress} instance. * @param message * byte[] * the message to sent. * @param tmStateReference * the (optional) transport model state reference as defined by * RFC 5590 section 6.1. * * @throws IOException * if an IO exception occurs while trying to send the message. */ public void sendMessage(TcpAddress address, byte[] message, TransportStateReference tmStateReference, long timeoutMillis, int maxRetries) throws java.io.IOException { if (getListenWorkerTask() == null || serverThread == null) { if (isOpenSocketOnSending()) { listen(); } else { handleDroppedMessageToSend(address, message, tmStateReference, timeoutMillis, maxRetries); } } if (serverThread != null) { if ((suspendedAddresses.size() > 0) && suspendedAddresses.contains(address)) { handleDroppedMessageToSend(address, message, tmStateReference, timeoutMillis, maxRetries); } else { serverThread.sendMessage(address, message, tmStateReference, sockets); } } } public MessageLengthDecoder getMessageLengthDecoder() { return messageLengthDecoder; } /** * Sets the message length decoder. Default message length decoder is the * {@link SnmpMesssageLengthDecoder}. The message length decoder must be * able to decode the total length of a message for this transport mapping * protocol(s). * * @param messageLengthDecoder * a {@code MessageLengthDecoder} instance. */ public void setMessageLengthDecoder(MessageLengthDecoder messageLengthDecoder) { if (messageLengthDecoder == null) { throw new NullPointerException(); } this.messageLengthDecoder = messageLengthDecoder; } /** * Gets the {@link CommonTimer} that controls socket cleanup operations. * * @return a socket cleaner timer. * @since 3.0 */ @Override public CommonTimer getSocketCleaner() { return super.getSocketCleaner(); } /** * Sets the maximum buffer size for incoming requests. When SNMP packets are * received that are longer than this maximum size, the messages will be * silently dropped and the connection will be closed. * * @param maxInboundMessageSize * the length of the inbound buffer in bytes. */ public void setMaxInboundMessageSize(int maxInboundMessageSize) { this.maxInboundMessageSize = maxInboundMessageSize; } protected static class SocketEntry extends AbstractSocketEntry { private ByteBuffer readBuffer = null; public SocketEntry(TcpAddress address, SocketChannel socketChannel) { super(address, socketChannel); // with TCP there is no handshaking, thus we are finished if connected setHandshakeFinished(true); } public void closeSession() { // nothing to do } @Override public Object getSessionID() { return socketChannel; } public void setReadBuffer(ByteBuffer byteBuffer) { this.readBuffer = byteBuffer; } public ByteBuffer getReadBuffer() { return readBuffer; } @Override public String toString() { return "SocketEntry[peerAddress=" + getPeerAddress() + ",socket=" + socketChannel + ",lastUse=" + new Date(getLastUse() / SnmpConstants.MILLISECOND_TO_NANOSECOND) + ",readBufferPosition=" + ((readBuffer == null) ? -1 : readBuffer.position()) + ",socketTimeout=" + getSocketTimeout() + "]"; } } public static class SnmpMesssageLengthDecoder implements MessageLengthDecoder { protected SnmpMesssageLengthDecoder() { } public int getMinHeaderLength() { return MIN_SNMP_HEADER_LENGTH; } public MessageLength getMessageLength(ByteBuffer buf) throws IOException { MutableByte type = new MutableByte(); BERInputStream is = new BERInputStream(buf); int ml = BER.decodeHeader(is, type, false); int hl = (int) is.getPosition(); return new MessageLength(hl, ml); } } @Override public TcpAddress getListenAddress() { int port = tcpAddress.getPort(); ServerThread serverThreadCopy = serverThread; try { port = ((InetSocketAddress) serverThreadCopy.ssc.getLocalAddress()).getPort(); } catch (NullPointerException npe) { // ignore } catch (IOException e) { e.printStackTrace(); } return new TcpAddress(tcpAddress.getInetAddress(), port); } protected class ServerThread extends AbstractTransportServerThread { protected byte[] buf; private Throwable lastError = null; public ServerThread() throws IOException { super(DefaultTcpTransportMapping.this, tcpAddress); buf = new byte[getMaxInboundMessageSize()]; // Selector for incoming requests selector = Selector.open(); if (isServerEnabled()) { // Create a new server socket and set to non blocking mode ssc = ServerSocketChannel.open(); try { ssc.configureBlocking(false); // Bind the server socket InetSocketAddress isa = new InetSocketAddress(tcpAddress.getInetAddress(), tcpAddress.getPort()); setSocketOptions(ssc.socket()); ssc.socket().bind(isa); // Register accepts on the server socket with the selector. This // step tells the selector that the socket wants to be put on the // ready list when accept operations occur, so allowing multiplexed // non-blocking I/O to take place. ssc.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException iox) { logger.warn("Socket bind failed for " + tcpAddress + ": " + iox.getMessage()); try { ssc.close(); } catch (IOException ioxClose) { logger.warn("Socket close failed after bind failure for " + tcpAddress + ": " + ioxClose.getMessage()); } throw iox; } } } public Throwable getLastError() { return lastError; } @Override protected SocketEntry createSocketEntry(TcpAddress address, SocketChannel socketChannel, boolean useClientMode, TransportStateReference tmStateReference) { return new SocketEntry(address, socketChannel); } @Override protected SocketChannel openSocketChannel(ProtocolFamily family) throws IOException { return SocketChannel.open(); } public void run() { // Here's where everything happens. The select method will // return when any operations registered above have occurred, the // thread has been interrupted, etc. doServer(sockets); /* try { while (!stop) { try { if (selector.select() > 0) { if (stop) { break; } // Someone is ready for I/O, get the ready keys Set readyKeys = selector.selectedKeys(); Iterator it = readyKeys.iterator(); // Walk through the ready keys collection and process date requests. while (it.hasNext()) { try { SelectionKey sk = it.next(); it.remove(); SocketChannel readChannel = null; TcpAddress incomingAddress = null; if (sk.isAcceptable()) { logger.debug("Key is acceptable"); // The key indexes into the selector, so you // can retrieve the socket that's ready for I/O ServerSocketChannel nextReady = (ServerSocketChannel) sk.channel(); Socket s = nextReady.accept().socket(); readChannel = s.getChannel(); readChannel.configureBlocking(false); incomingAddress = createIncomingAddress(s); SocketEntry entry = createSocketEntry(incomingAddress, s, false, null); entry.addRegistration(selector, SelectionKey.OP_READ); sockets.put(incomingAddress, entry); timeoutSocket(entry); TransportStateEvent e = new TransportStateEvent(DefaultTcpTransportMapping.this, incomingAddress, TransportStateEvent.STATE_CONNECTED, null); fireConnectionStateChanged(e); if (e.isCancelled()) { logger.warn("Incoming connection cancelled"); s.close(); removeSocketEntry(incomingAddress); readChannel = null; } } else if (sk.isConnectable()) { logger.debug("Key is connectable"); connectChannel(sk, incomingAddress); } else { if (sk.isWritable()) { logger.debug("Key is writable"); incomingAddress = writeData(sk, incomingAddress); } if (sk.isReadable()) { logger.debug("Key is readable"); readChannel = (SocketChannel) sk.channel(); incomingAddress = createIncomingAddress(readChannel.socket()); } } if (readChannel != null) { logger.debug("Key is reading"); try { SocketEntry entry = (SocketEntry) sk.attachment(); if (!readMessage(sk, readChannel, incomingAddress, entry)) { if ((entry != null) && (getMaxBusyLoops() > 0)) { int busyLoops = entry.nextBusyLoop(); if (busyLoops > getMaxBusyLoops()) { if (logger.isDebugEnabled()) { logger.debug("After " + busyLoops + " read key has been removed: " + entry); } entry.removeRegistration(selector, SelectionKey.OP_READ); entry.resetBusyLoops(); } } } } catch (IOException iox) { // IO exception -> channel closed remotely socketClosedRemotely(sk, readChannel, incomingAddress); } } } catch (CancelledKeyException ckex) { if (logger.isDebugEnabled()) { logger.debug("Selection key cancelled, skipping it"); } } } } } catch (NullPointerException npex) { // There seems to happen a NullPointerException within the select() npex.printStackTrace(); logger.warn("NullPointerException within select()?"); stop = true; } if (!stop) { processPending(); } } if (ssc != null) { ssc.close(); } if (selector != null) { selector.close(); } } catch (IOException iox) { logger.error(iox); lastError = iox; } if (!stop) { stop = true; synchronized (DefaultTcpTransportMapping.this) { listenWorkerTask = null; } } if (logger.isDebugEnabled()) { logger.debug("Worker task finished: " + getClass().getName()); } */ } @Override protected boolean readMessage(SelectionKey sk, SocketChannel readChannel, TcpAddress incomingAddress, SocketEntry socketEntry) throws IOException { SocketEntry entry = (SocketEntry) sk.attachment(); if (entry == null) { // slow but in some cases needed: entry = sockets.get(incomingAddress); } if (entry != null) { // note that socket has been used entry.used(); ByteBuffer readBuffer = entry.getReadBuffer(); if (readBuffer != null) { int bytesRead = readChannel.read(readBuffer); if (logger.isDebugEnabled()) { logger.debug("Read " + bytesRead + " bytes from " + incomingAddress); } if ((bytesRead >= 0) && (readBuffer.hasRemaining() || (readBuffer.position() < messageLengthDecoder.getMinHeaderLength()))) { entry.addRegistration(selector, SelectionKey.OP_READ); } else if (bytesRead < 0) { socketClosedRemotely(sk, readChannel, incomingAddress); } else { readSnmpMessagePayload(readChannel, incomingAddress, entry, readBuffer); } if (bytesRead != 0) { entry.resetBusyLoops(); return true; } return false; } } ByteBuffer byteBuffer = ByteBuffer.wrap(buf); byteBuffer.limit(messageLengthDecoder.getMinHeaderLength()); if (!readChannel.isOpen()) { cancelNonServerSelectionKey(sk); if (logger.isDebugEnabled()) { logger.debug("Read channel not open, no bytes read from " + incomingAddress); } return false; } long bytesRead; try { bytesRead = readChannel.read(byteBuffer); if (logger.isDebugEnabled()) { logger.debug("Reading header " + bytesRead + " bytes from " + incomingAddress); } } catch (ClosedChannelException ccex) { cancelNonServerSelectionKey(sk); if (logger.isDebugEnabled()) { logger.debug("Read channel not open, no bytes read from " + incomingAddress); } return false; } if (byteBuffer.position() >= messageLengthDecoder.getMinHeaderLength()) { readSnmpMessagePayload(readChannel, incomingAddress, entry, byteBuffer); } else if (bytesRead < 0) { socketClosedRemotely(sk, readChannel, incomingAddress); } else if ((entry != null) && (bytesRead > 0)) { addBufferToReadBuffer(entry, byteBuffer); entry.addRegistration(selector, SelectionKey.OP_READ); } else { if (logger.isDebugEnabled()) { logger.debug("No socket entry found for incoming address " + incomingAddress + " for incomplete message with length " + bytesRead); } } if ((entry != null) && (bytesRead != 0)) { entry.resetBusyLoops(); return true; } return false; } @Override protected void processQueues() { } @Override public SocketEntry removeSocketEntry(TcpAddress incomingAddress) { return sockets.remove(incomingAddress); } protected TcpAddress createIncomingAddress(SocketChannel sc) throws IOException { Socket s = sc.socket(); return new TcpAddress(s.getInetAddress(), s.getPort()); } protected void readSnmpMessagePayload(SocketChannel readChannel, TcpAddress incomingAddress, SocketEntry entry, ByteBuffer byteBuffer) throws IOException { MessageLength messageLength = messageLengthDecoder.getMessageLength(ByteBuffer.wrap(byteBuffer.array())); if (logger.isDebugEnabled()) { logger.debug("Message length is " + messageLength); } if ((messageLength.getMessageLength() > getMaxInboundMessageSize()) || (messageLength.getMessageLength() <= 0)) { logger.error("Received message length " + messageLength + " is greater than inboundBufferSize " + getMaxInboundMessageSize()); if (entry != null) { Socket s = entry.getSocketChannel().socket(); if (s != null) { s.close(); logger.info("Socket to " + entry.getPeerAddress() + " closed due to an error"); } } } else { int messageSize = messageLength.getMessageLength(); if (byteBuffer.position() < messageSize) { if (byteBuffer.capacity() < messageSize) { if (logger.isDebugEnabled()) { logger.debug("Extending message buffer size according to message length to " + messageSize); } // Enhance capacity to expected message size and replace existing (too short) read buffer byte[] newBuffer = new byte[messageSize]; int len = byteBuffer.position(); byteBuffer.flip(); byteBuffer.get(newBuffer, 0, len); byteBuffer = ByteBuffer.wrap(newBuffer); byteBuffer.position(len); if (entry != null) { byteBuffer.limit(messageSize); entry.setReadBuffer(byteBuffer); } } else { byteBuffer.limit(messageSize); } readChannel.read(byteBuffer); } long bytesRead = byteBuffer.position(); if (bytesRead >= messageSize) { if (logger.isDebugEnabled()) { logger.debug("Message completed with " + bytesRead + " bytes and " + byteBuffer.limit() + " buffer limit"); } if (entry != null) { entry.setReadBuffer(null); } dispatchMessage(incomingAddress, byteBuffer, bytesRead, entry); } else if ((entry != null) && (byteBuffer != entry.getReadBuffer())) { if (logger.isDebugEnabled()) { logger.debug("Adding buffer content to read buffer of entry " + entry + ", buffer " + byteBuffer); } addBufferToReadBuffer(entry, byteBuffer); } if (entry != null) { entry.addRegistration(selector, SelectionKey.OP_READ); } } } private void dispatchMessage(TcpAddress incomingAddress, ByteBuffer byteBuffer, long bytesRead, Object sessionID) { byteBuffer.flip(); if (logger.isDebugEnabled()) { logger.debug("Received message from " + incomingAddress + " with length " + bytesRead + ": " + new OctetString(byteBuffer.array(), 0, (int) bytesRead).toHexString()); } ByteBuffer bis; if (isAsyncMsgProcessingSupported()) { byte[] bytes = new byte[(int) bytesRead]; System.arraycopy(byteBuffer.array(), 0, bytes, 0, (int) bytesRead); bis = ByteBuffer.wrap(bytes); } else { bis = ByteBuffer.wrap(byteBuffer.array(), 0, (int) bytesRead); } TransportStateReference stateReference = new TransportStateReference(DefaultTcpTransportMapping.this, incomingAddress, null, SecurityLevel.undefined, SecurityLevel.undefined, false, sessionID); fireProcessMessage(incomingAddress, bis, stateReference); } } @Override public void wakeupServerSelector() { serverThread.selector.wakeup(); } protected void addBufferToReadBuffer(SocketEntry entry, ByteBuffer byteBuffer) { if (logger.isDebugEnabled()) { logger.debug("Adding data " + byteBuffer + " to read buffer " + entry.getReadBuffer()); } int buflen = byteBuffer.position(); if (entry.getReadBuffer() != null) { entry.getReadBuffer().put(byteBuffer.array(), 0, buflen); } else { byte[] message = new byte[byteBuffer.limit()]; byteBuffer.flip(); byteBuffer.get(message, 0, buflen); ByteBuffer newBuffer = ByteBuffer.wrap(message); newBuffer.position(buflen); entry.setReadBuffer(newBuffer); } } protected void socketClosedRemotely(SelectionKey sk, SocketChannel readChannel, TcpAddress incomingAddress) throws IOException { logger.debug("Socket closed remotely"); cancelNonServerSelectionKey(sk); readChannel.close(); TransportStateEvent e = new TransportStateEvent(DefaultTcpTransportMapping.this, incomingAddress, TransportStateEvent.STATE_DISCONNECTED_REMOTELY, null); fireConnectionStateChanged(e); sockets.remove(incomingAddress); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy