341 lines
13 KiB
Java
341 lines
13 KiB
Java
/*
|
|
* 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) {
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|