1 /*
2  * Conditions Of Use
3  *
4  * This software was developed by employees of the National Institute of
5  * Standards and Technology (NIST), an agency of the Federal Government.
6  * Pursuant to title 15 United States Code Section 105, works of NIST
7  * employees are not subject to copyright protection in the United States
8  * and are considered to be in the public domain.  As a result, a formal
9  * license is not needed to use the software.
10  *
11  * This software is provided by NIST as a service and is expressly
12  * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
13  * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
14  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
15  * AND DATA ACCURACY.  NIST does not warrant or make any representations
16  * regarding the use of the software or the results thereof, including but
17  * not limited to the correctness, accuracy, reliability or usefulness of
18  * the software.
19  *
20  * Permission to use this software is contingent upon your acceptance
21  * of the terms of this agreement
22  *
23  * .
24  *
25  */
26 /*******************************************************************************
27  * Product of NIST/ITL Advanced Networking Technologies Division (ANTD).       *
28  *******************************************************************************/
29 package gov.nist.javax.sip.stack;
30 
31 import gov.nist.core.StackLogger;
32 import gov.nist.javax.sip.SipStackImpl;
33 
34 import java.io.*;
35 import java.net.*;
36 import java.util.Enumeration;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.Semaphore;
39 import java.util.concurrent.TimeUnit;
40 
41 import javax.net.ssl.HandshakeCompletedListener;
42 import javax.net.ssl.SSLSocket;
43 
44 /*
45  * TLS support Added by Daniel J.Martinez Manzano <dani@dif.um.es>
46  *
47  */
48 
49 /**
50  * Low level Input output to a socket. Caches TCP connections and takes care of re-connecting to
51  * the remote party if the other end drops the connection
52  *
53  * @version 1.2
54  *
55  * @author M. Ranganathan <br/>
56  *
57  *
58  */
59 
60 class IOHandler {
61 
62     private Semaphore ioSemaphore = new Semaphore(1);
63 
64     private SipStackImpl sipStack;
65 
66     private static String TCP = "tcp";
67 
68     // Added by Daniel J. Martinez Manzano <dani@dif.um.es>
69     private static String TLS = "tls";
70 
71     // A cache of client sockets that can be re-used for
72     // sending tcp messages.
73     private ConcurrentHashMap<String, Socket> socketTable;
74 
makeKey(InetAddress addr, int port)75     protected static String makeKey(InetAddress addr, int port) {
76         return addr.getHostAddress() + ":" + port;
77 
78     }
79 
IOHandler(SIPTransactionStack sipStack)80     protected IOHandler(SIPTransactionStack sipStack) {
81         this.sipStack = (SipStackImpl) sipStack;
82         this.socketTable = new ConcurrentHashMap<String, Socket>();
83 
84     }
85 
putSocket(String key, Socket sock)86     protected void putSocket(String key, Socket sock) {
87         socketTable.put(key, sock);
88 
89     }
90 
getSocket(String key)91     protected Socket getSocket(String key) {
92         return (Socket) socketTable.get(key);
93     }
94 
removeSocket(String key)95     protected void removeSocket(String key) {
96         socketTable.remove(key);
97     }
98 
99     /**
100      * A private function to write things out. This needs to be synchronized as writes can occur
101      * from multiple threads. We write in chunks to allow the other side to synchronize for large
102      * sized writes.
103      */
writeChunks(OutputStream outputStream, byte[] bytes, int length)104     private void writeChunks(OutputStream outputStream, byte[] bytes, int length)
105             throws IOException {
106         // Chunk size is 16K - this hack is for large
107         // writes over slow connections.
108         synchronized (outputStream) {
109             // outputStream.write(bytes,0,length);
110             int chunksize = 8 * 1024;
111             for (int p = 0; p < length; p += chunksize) {
112                 int chunk = p + chunksize < length ? chunksize : length - p;
113                 outputStream.write(bytes, p, chunk);
114             }
115         }
116         outputStream.flush();
117     }
118 
119     /**
120      * Creates and binds, if necessary, a socket connected to the specified destination address
121      * and port and then returns its local address.
122      *
123      * @param dst the destination address that the socket would need to connect to.
124      * @param dstPort the port number that the connection would be established with.
125      * @param localAddress the address that we would like to bind on (null for the "any" address).
126      * @param localPort the port that we'd like our socket to bind to (0 for a random port).
127      *
128      * @return the SocketAddress that this handler would use when connecting to the specified
129      *         destination address and port.
130      *
131      * @throws IOException
132      */
133     public SocketAddress obtainLocalAddress(InetAddress dst, int dstPort,
134             InetAddress localAddress, int localPort) throws IOException {
135         String key = makeKey(dst, dstPort);
136 
137         Socket clientSock = getSocket(key);
138 
139         if (clientSock == null) {
140             clientSock = sipStack.getNetworkLayer().createSocket(dst, dstPort, localAddress,
141                     localPort);
142             putSocket(key, clientSock);
143         }
144 
145         return clientSock.getLocalSocketAddress();
146 
147     }
148 
149     /**
150      * Send an array of bytes.
151      *
152      * @param receiverAddress -- inet address
153      * @param contactPort -- port to connect to.
154      * @param transport -- tcp or udp.
155      * @param retry -- retry to connect if the other end closed connection
156      * @throws IOException -- if there is an IO exception sending message.
157      */
158 
159     public Socket sendBytes(InetAddress senderAddress, InetAddress receiverAddress,
160             int contactPort, String transport, byte[] bytes, boolean retry,
161             MessageChannel messageChannel) throws IOException {
162         int retry_count = 0;
163         int max_retry = retry ? 2 : 1;
164         // Server uses TCP transport. TCP client sockets are cached
165         int length = bytes.length;
166         if (sipStack.isLoggingEnabled()) {
167             sipStack.getStackLogger().logDebug(
168                     "sendBytes " + transport + " inAddr " + receiverAddress.getHostAddress()
169                             + " port = " + contactPort + " length = " + length);
170         }
171         if (sipStack.isLoggingEnabled() && sipStack.isLogStackTraceOnMessageSend()) {
172             sipStack.getStackLogger().logStackTrace(StackLogger.TRACE_INFO);
173         }
174         if (transport.compareToIgnoreCase(TCP) == 0) {
175             String key = makeKey(receiverAddress, contactPort);
176             // This should be in a synchronized block ( reported by
177             // Jayashenkhar ( lucent ).
178 
179             try {
180                 boolean retval = this.ioSemaphore.tryAcquire(10000, TimeUnit.MILLISECONDS);
181                 if (!retval) {
182                     throw new IOException(
183                             "Could not acquire IO Semaphore after 10 seconds -- giving up ");
184                 }
185             } catch (InterruptedException ex) {
186                 throw new IOException("exception in acquiring sem");
187             }
188             Socket clientSock = getSocket(key);
189 
190             try {
191 
192                 while (retry_count < max_retry) {
193                     if (clientSock == null) {
194                         if (sipStack.isLoggingEnabled()) {
195                             sipStack.getStackLogger().logDebug("inaddr = " + receiverAddress);
196                             sipStack.getStackLogger().logDebug("port = " + contactPort);
197                         }
198                         // note that the IP Address for stack may not be
199                         // assigned.
200                         // sender address is the address of the listening point.
201                         // in version 1.1 all listening points have the same IP
202                         // address (i.e. that of the stack). In version 1.2
203                         // the IP address is on a per listening point basis.
204                         clientSock = sipStack.getNetworkLayer().createSocket(receiverAddress,
205                                 contactPort, senderAddress);
206                         OutputStream outputStream = clientSock.getOutputStream();
207                         writeChunks(outputStream, bytes, length);
208                         putSocket(key, clientSock);
209                         break;
210                     } else {
211                         try {
212                             OutputStream outputStream = clientSock.getOutputStream();
213                             writeChunks(outputStream, bytes, length);
214                             break;
215                         } catch (IOException ex) {
216                             if (sipStack.isLoggingEnabled())
217                                 sipStack.getStackLogger().logDebug(
218                                         "IOException occured retryCount " + retry_count);
219                             // old connection is bad.
220                             // remove from our table.
221                             removeSocket(key);
222                             try {
223                                 clientSock.close();
224                             } catch (Exception e) {
225                             }
226                             clientSock = null;
227                             retry_count++;
228                         }
229                     }
230                 }
231             } finally {
232                 ioSemaphore.release();
233             }
234 
235             if (clientSock == null) {
236 
237                 if (sipStack.isLoggingEnabled()) {
238                     sipStack.getStackLogger().logDebug(this.socketTable.toString());
239                     sipStack.getStackLogger().logError(
240                             "Could not connect to " + receiverAddress + ":" + contactPort);
241                 }
242 
243                 throw new IOException("Could not connect to " + receiverAddress + ":"
244                         + contactPort);
245             } else
246                 return clientSock;
247 
248             // Added by Daniel J. Martinez Manzano <dani@dif.um.es>
249             // Copied and modified from the former section for TCP
250         } else if (transport.compareToIgnoreCase(TLS) == 0) {
251             String key = makeKey(receiverAddress, contactPort);
252             try {
253                 boolean retval = this.ioSemaphore.tryAcquire(10000, TimeUnit.MILLISECONDS);
254                 if (!retval)
255                     throw new IOException("Timeout acquiring IO SEM");
256             } catch (InterruptedException ex) {
257                 throw new IOException("exception in acquiring sem");
258             }
259             Socket clientSock = getSocket(key);
260 
261             try {
262                 while (retry_count < max_retry) {
263                     if (clientSock == null) {
264                         if (sipStack.isLoggingEnabled()) {
265                             sipStack.getStackLogger().logDebug("inaddr = " + receiverAddress);
266                             sipStack.getStackLogger().logDebug("port = " + contactPort);
267                         }
268 
269                         clientSock = sipStack.getNetworkLayer().createSSLSocket(receiverAddress,
270                                 contactPort, senderAddress);
271                         SSLSocket sslsock = (SSLSocket) clientSock;
272                         HandshakeCompletedListener listner = new HandshakeCompletedListenerImpl(
273                                 (TLSMessageChannel) messageChannel);
274                         ((TLSMessageChannel) messageChannel)
275                                 .setHandshakeCompletedListener(listner);
276                         sslsock.addHandshakeCompletedListener(listner);
277                         sslsock.setEnabledProtocols(sipStack.getEnabledProtocols());
278                         sslsock.startHandshake();
279 
280                         OutputStream outputStream = clientSock.getOutputStream();
281                         writeChunks(outputStream, bytes, length);
282                         putSocket(key, clientSock);
283                         break;
284                     } else {
285                         try {
286                             OutputStream outputStream = clientSock.getOutputStream();
287                             writeChunks(outputStream, bytes, length);
288                             break;
289                         } catch (IOException ex) {
290                             if (sipStack.isLoggingEnabled())
291                                 sipStack.getStackLogger().logException(ex);
292                             // old connection is bad.
293                             // remove from our table.
294                             removeSocket(key);
295                             try {
296                                 clientSock.close();
297                             } catch (Exception e) {
298                             }
299                             clientSock = null;
300                             retry_count++;
301                         }
302                     }
303                 }
304             } finally {
305                 ioSemaphore.release();
306             }
307             if (clientSock == null) {
308                 throw new IOException("Could not connect to " + receiverAddress + ":"
309                         + contactPort);
310             } else
311                 return clientSock;
312 
313         } else {
314             // This is a UDP transport...
315             DatagramSocket datagramSock = sipStack.getNetworkLayer().createDatagramSocket();
316             datagramSock.connect(receiverAddress, contactPort);
317             DatagramPacket dgPacket = new DatagramPacket(bytes, 0, length, receiverAddress,
318                     contactPort);
319             datagramSock.send(dgPacket);
320             datagramSock.close();
321             return null;
322         }
323 
324     }
325 
326     /**
327      * Close all the cached connections.
328      */
329     public void closeAll() {
330         for (Enumeration<Socket> values = socketTable.elements(); values.hasMoreElements();) {
331             Socket s = (Socket) values.nextElement();
332             try {
333                 s.close();
334             } catch (IOException ex) {
335             }
336         }
337 
338     }
339 
340 }
341