/* * Conditions Of Use * * This software was developed by employees of the National Institute of * Standards and Technology (NIST), an agency of the Federal Government. * Pursuant to title 15 United States Code Section 105, works of NIST * employees are not subject to copyright protection in the United States * and are considered to be in the public domain. As a result, a formal * license is not needed to use the software. * * This software is provided by NIST as a service and is expressly * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT * AND DATA ACCURACY. NIST does not warrant or make any representations * regarding the use of the software or the results thereof, including but * not limited to the correctness, accuracy, reliability or usefulness of * the software. * * Permission to use this software is contingent upon your acceptance * of the terms of this agreement * * . * */ /******************************************************************************* * Product of NIST/ITL Advanced Networking Technologies Division (ANTD). * *******************************************************************************/ package gov.nist.javax.sip.stack; import gov.nist.core.StackLogger; import gov.nist.javax.sip.SipStackImpl; import java.io.*; import java.net.*; import java.util.Enumeration; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLSocket; /* * TLS support Added by Daniel J.Martinez Manzano <dani@dif.um.es> * */ /** * Low level Input output to a socket. Caches TCP connections and takes care of re-connecting to * the remote party if the other end drops the connection * * @version 1.2 * * @author M. Ranganathan <br/> * * */ class IOHandler { private Semaphore ioSemaphore = new Semaphore(1); private SipStackImpl sipStack; private static String TCP = "tcp"; // Added by Daniel J. Martinez Manzano <dani@dif.um.es> private static String TLS = "tls"; // A cache of client sockets that can be re-used for // sending tcp messages. private ConcurrentHashMap<String, Socket> socketTable; protected static String makeKey(InetAddress addr, int port) { return addr.getHostAddress() + ":" + port; } protected IOHandler(SIPTransactionStack sipStack) { this.sipStack = (SipStackImpl) sipStack; this.socketTable = new ConcurrentHashMap<String, Socket>(); } protected void putSocket(String key, Socket sock) { socketTable.put(key, sock); } protected Socket getSocket(String key) { return (Socket) socketTable.get(key); } protected void removeSocket(String key) { socketTable.remove(key); } /** * A private function to write things out. This needs to be synchronized as writes can occur * from multiple threads. We write in chunks to allow the other side to synchronize for large * sized writes. */ private void writeChunks(OutputStream outputStream, byte[] bytes, int length) throws IOException { // Chunk size is 16K - this hack is for large // writes over slow connections. synchronized (outputStream) { // outputStream.write(bytes,0,length); int chunksize = 8 * 1024; for (int p = 0; p < length; p += chunksize) { int chunk = p + chunksize < length ? chunksize : length - p; outputStream.write(bytes, p, chunk); } } outputStream.flush(); } /** * Creates and binds, if necessary, a socket connected to the specified destination address * and port and then returns its local address. * * @param dst the destination address that the socket would need to connect to. * @param dstPort the port number that the connection would be established with. * @param localAddress the address that we would like to bind on (null for the "any" address). * @param localPort the port that we'd like our socket to bind to (0 for a random port). * * @return the SocketAddress that this handler would use when connecting to the specified * destination address and port. * * @throws IOException */ public SocketAddress obtainLocalAddress(InetAddress dst, int dstPort, InetAddress localAddress, int localPort) throws IOException { String key = makeKey(dst, dstPort); Socket clientSock = getSocket(key); if (clientSock == null) { clientSock = sipStack.getNetworkLayer().createSocket(dst, dstPort, localAddress, localPort); putSocket(key, clientSock); } return clientSock.getLocalSocketAddress(); } /** * Send an array of bytes. * * @param receiverAddress -- inet address * @param contactPort -- port to connect to. * @param transport -- tcp or udp. * @param retry -- retry to connect if the other end closed connection * @throws IOException -- if there is an IO exception sending message. */ public Socket sendBytes(InetAddress senderAddress, InetAddress receiverAddress, int contactPort, String transport, byte[] bytes, boolean retry, MessageChannel messageChannel) throws IOException { int retry_count = 0; int max_retry = retry ? 2 : 1; // Server uses TCP transport. TCP client sockets are cached int length = bytes.length; if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug( "sendBytes " + transport + " inAddr " + receiverAddress.getHostAddress() + " port = " + contactPort + " length = " + length); } if (sipStack.isLoggingEnabled() && sipStack.isLogStackTraceOnMessageSend()) { sipStack.getStackLogger().logStackTrace(StackLogger.TRACE_INFO); } if (transport.compareToIgnoreCase(TCP) == 0) { String key = makeKey(receiverAddress, contactPort); // This should be in a synchronized block ( reported by // Jayashenkhar ( lucent ). try { boolean retval = this.ioSemaphore.tryAcquire(10000, TimeUnit.MILLISECONDS); if (!retval) { throw new IOException( "Could not acquire IO Semaphore after 10 seconds -- giving up "); } } catch (InterruptedException ex) { throw new IOException("exception in acquiring sem"); } Socket clientSock = getSocket(key); try { while (retry_count < max_retry) { if (clientSock == null) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("inaddr = " + receiverAddress); sipStack.getStackLogger().logDebug("port = " + contactPort); } // note that the IP Address for stack may not be // assigned. // sender address is the address of the listening point. // in version 1.1 all listening points have the same IP // address (i.e. that of the stack). In version 1.2 // the IP address is on a per listening point basis. clientSock = sipStack.getNetworkLayer().createSocket(receiverAddress, contactPort, senderAddress); OutputStream outputStream = clientSock.getOutputStream(); writeChunks(outputStream, bytes, length); putSocket(key, clientSock); break; } else { try { OutputStream outputStream = clientSock.getOutputStream(); writeChunks(outputStream, bytes, length); break; } catch (IOException ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logDebug( "IOException occured retryCount " + retry_count); // old connection is bad. // remove from our table. removeSocket(key); try { clientSock.close(); } catch (Exception e) { } clientSock = null; retry_count++; } } } } finally { ioSemaphore.release(); } if (clientSock == null) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug(this.socketTable.toString()); sipStack.getStackLogger().logError( "Could not connect to " + receiverAddress + ":" + contactPort); } throw new IOException("Could not connect to " + receiverAddress + ":" + contactPort); } else return clientSock; // Added by Daniel J. Martinez Manzano <dani@dif.um.es> // Copied and modified from the former section for TCP } else if (transport.compareToIgnoreCase(TLS) == 0) { String key = makeKey(receiverAddress, contactPort); try { boolean retval = this.ioSemaphore.tryAcquire(10000, TimeUnit.MILLISECONDS); if (!retval) throw new IOException("Timeout acquiring IO SEM"); } catch (InterruptedException ex) { throw new IOException("exception in acquiring sem"); } Socket clientSock = getSocket(key); try { while (retry_count < max_retry) { if (clientSock == null) { if (sipStack.isLoggingEnabled()) { sipStack.getStackLogger().logDebug("inaddr = " + receiverAddress); sipStack.getStackLogger().logDebug("port = " + contactPort); } clientSock = sipStack.getNetworkLayer().createSSLSocket(receiverAddress, contactPort, senderAddress); SSLSocket sslsock = (SSLSocket) clientSock; HandshakeCompletedListener listner = new HandshakeCompletedListenerImpl( (TLSMessageChannel) messageChannel); ((TLSMessageChannel) messageChannel) .setHandshakeCompletedListener(listner); sslsock.addHandshakeCompletedListener(listner); sslsock.setEnabledProtocols(sipStack.getEnabledProtocols()); sslsock.startHandshake(); OutputStream outputStream = clientSock.getOutputStream(); writeChunks(outputStream, bytes, length); putSocket(key, clientSock); break; } else { try { OutputStream outputStream = clientSock.getOutputStream(); writeChunks(outputStream, bytes, length); break; } catch (IOException ex) { if (sipStack.isLoggingEnabled()) sipStack.getStackLogger().logException(ex); // old connection is bad. // remove from our table. removeSocket(key); try { clientSock.close(); } catch (Exception e) { } clientSock = null; retry_count++; } } } } finally { ioSemaphore.release(); } if (clientSock == null) { throw new IOException("Could not connect to " + receiverAddress + ":" + contactPort); } else return clientSock; } else { // This is a UDP transport... DatagramSocket datagramSock = sipStack.getNetworkLayer().createDatagramSocket(); datagramSock.connect(receiverAddress, contactPort); DatagramPacket dgPacket = new DatagramPacket(bytes, 0, length, receiverAddress, contactPort); datagramSock.send(dgPacket); datagramSock.close(); return null; } } /** * Close all the cached connections. */ public void closeAll() { for (Enumeration<Socket> values = socketTable.elements(); values.hasMoreElements();) { Socket s = (Socket) values.nextElement(); try { s.close(); } catch (IOException ex) { } } } }