View Javadoc

1   package au.com.loftinspace.tcpreflector;
2   
3   import au.com.loftinspace.tcpreflector.packet.PacketListener;
4   
5   import java.io.IOException;
6   import java.net.InetSocketAddress;
7   import java.net.ServerSocket;
8   import java.net.Socket;
9   import java.net.SocketTimeoutException;
10  import java.util.ArrayList;
11  import java.util.Collections;
12  import java.util.Iterator;
13  import java.util.LinkedList;
14  import java.util.List;
15  
16  /***
17   * Accepts client socket connections and establishes reflector sessions for each new connection.
18   * @author Jem Mawson
19   */
20  public class TcpReflector implements Runnable {
21  
22      private static final int SERVER_SOCKET_TIMEOUT = 2000;
23  
24      private ServerSocket serverSocket;
25      private InetSocketAddress destination;
26      private List reflectors;
27      private List listeners;
28      private boolean reflective;
29      private boolean terminated;
30      private boolean connective;
31      private boolean waitingForNewAcceptAttempt;
32      private int listeningPort;
33  
34      public TcpReflector(int listeningPort, InetSocketAddress destination) throws IOException {
35          this.listeningPort = listeningPort;
36          this.destination = destination;
37          this.reflectors = new ArrayList();
38          this.listeners = new LinkedList();
39          this.reflective = true;
40          this.connective = true;
41          initialiseServerSocket();
42      }
43  
44      public void run() {
45          try {
46              while (!terminated) {
47                  acceptConnection();
48              }
49          } catch (Throwable t) {
50              // ignored
51          } finally {
52              shutdown();
53          }
54      }
55  
56      /***
57       * Whether this reflector should discard or redirect both incoming and outgoing packets.
58       * @param reflective
59       * 		<code>true</code> to redirect packets.<br>
60       *  	<code>false</code> to discard them.
61       */
62      public void setReflective(boolean reflective) {
63          Iterator iter = reflectors.iterator();
64          while (iter.hasNext()) {
65              Reflector reflector = (Reflector)iter.next();
66              reflector.setReflective(reflective);
67          }
68          this.reflective = reflective;
69      }
70  
71      /***
72       * Whether this reflector should discard or redirect incoming packets.
73       * @param reflective
74       * 		<code>true</code> to deliver incoming packets.<br>
75       *  	<code>false</code> to discard them.
76       */
77      public void setResponseReflective(boolean reflective) {
78          Iterator iter = reflectors.iterator();
79          while (iter.hasNext()) {
80              Reflector reflector = (Reflector)iter.next();
81              reflector.setResponseReflective(reflective);
82          }
83          this.reflective = reflective;
84      }
85  
86      /***
87       * Whether this reflector is redirecting packets.
88       * @return
89       * 		<code>true</code> if it redirects packets.<br>
90       *  	<code>false</code> if it discards them.
91       */
92      public boolean isReflective() {
93          return reflective;
94      }
95  
96      /***
97       * Returns an unmodifiable list of all PacketListeners associated with this instance.
98       * @return
99       *      A list of PacketListeners
100      */
101     public List getPacketListeners() {
102         return Collections.unmodifiableList(listeners);
103     }
104 
105     /***
106      * Adds a packet listener to this instance. The listener will be called upon to take action
107      * whenever a packet is transmitted in either direction. This action affects both existing
108      * connections and new connections.
109      */
110     public void addListener(PacketListener listener) {
111         listeners.add(listener);
112         Iterator iter = reflectors.iterator();
113         while (iter.hasNext()) {
114             Reflector reflector = (Reflector) iter.next();
115             reflector.addListener(listener);
116         }
117     }
118 
119     /***
120      * Removes a PacketListener from this instance. The listener will no longer be called upon to
121      * handle packets on conenctions originating from this instance. This action affects both
122      * existing connections and new connections.
123      */
124     public void removeListener(PacketListener listener) {
125         listeners.remove(listener);
126         Iterator iter = reflectors.iterator();
127         while (iter.hasNext()) {
128             Reflector reflector = (Reflector) iter.next();
129             reflector.removeListener(listener);
130         }
131     }
132 
133     /***
134      * Removes all PacketListeners from this instance. None of the packets on connections
135      * originating from this instance will be handled by any PacketListeners until such time as a
136      * new PacketListener is appended. This action affects both existing connections and new
137      * connections.
138      */
139     public void removeAllListeners() {
140         listeners.clear();
141         Iterator iter = reflectors.iterator();
142         while (iter.hasNext()) {
143             Reflector reflector = (Reflector) iter.next();
144             reflector.removeAllListeners();
145         }
146     }
147 
148     /***
149      * Get the destination internet address for connections.
150      * @return
151      *      The destination internet address for connections.
152      */
153     public InetSocketAddress getDestination() {
154         return destination;
155     }
156 
157     /***
158      * Set the destination internet address for future connections. This does not affect currently
159      * active connections.
160      * @param destination
161      *      The destination for future connections.
162      */
163     public void setDestination(InetSocketAddress destination) {
164         this.destination = destination;
165     }
166 
167     /***
168      * Returns whether this instance will accept new socket connections. If this is false then
169      * connection attempts will result in an IOException.
170      * @return
171      *      Whether this instance will accept new socket connections.
172      */
173     public boolean isConnective() {
174         return connective;
175     }
176 
177     /***
178      * Sets whether this instance will accept connections. This method will quick return and does
179      * not guarantee that a change from true to false will take effect immediately.
180      *
181      * until the
182      * instance state has been effectively changed. In practice this may mean waiting until any
183      * current socket accept attempt has timed out.
184      * @param connective
185      *      Whether this instance should accept connections.
186      * @throws IOException
187      *      If a problem is encountered establishing or shutting down the underlying ServerSocket
188      *      connection.
189      */
190     public void setConnective(boolean connective) throws IOException {
191         if (this.connective == connective) {
192             return;
193         }
194         this.connective = connective;
195         waitingForNewAcceptAttempt = true;
196 
197         if (connective) {
198             initialiseServerSocket();
199             blockUntilNewConnectAttempt();
200         } else {
201             blockUntilNewConnectAttempt();
202             shutdownServerSocket();
203         }
204     }
205 
206     /***
207      * Causes the reflector to terminate all processing. The underlying socket
208      * connection will be lost. This action is unrecoverable.
209      */
210     public synchronized void shutdown() {
211         if (terminated) {
212             return;
213         }
214         if (serverSocket != null) {
215             try {
216                 serverSocket.close();
217             } catch (IOException ignored) {
218             }
219             serverSocket = null;
220         }
221         Iterator iter = reflectors.iterator();
222         while (iter.hasNext()) {
223             Reflector reflector = (Reflector)iter.next();
224             reflector.terminateInterceptors();
225         }
226         terminated = true;
227     }
228 
229     private void acceptConnection() throws IOException {
230         if (!connective) {
231             sleep(SERVER_SOCKET_TIMEOUT);
232             return;
233         }
234 
235         Socket clientSocket;
236         try {
237             waitingForNewAcceptAttempt = false;
238             clientSocket = serverSocket.accept();
239         } catch (SocketTimeoutException e) {
240             return;
241         }
242         Socket destinationSocket = new Socket(destination.getHostName(), destination.getPort());
243         Reflector reflector = new Reflector(clientSocket, destinationSocket);
244         reflector.setReflective(reflective);
245         Iterator iter = listeners.iterator();
246         while (iter.hasNext()) {
247             reflector.addListener((PacketListener) iter.next());
248         }
249         reflector.startReflecting();
250         reflectors.add(reflector);
251     }
252 
253     private void initialiseServerSocket() throws IOException {
254         serverSocket = new ServerSocket(listeningPort);
255         serverSocket.setSoTimeout(SERVER_SOCKET_TIMEOUT);
256     }
257 
258     private void shutdownServerSocket() throws IOException {
259         serverSocket.close();
260         serverSocket = null;
261     }
262 
263     private void blockUntilNewConnectAttempt() {
264         long endtime = System.currentTimeMillis() + (SERVER_SOCKET_TIMEOUT * 2);
265         while (waitingForNewAcceptAttempt && (System.currentTimeMillis() < endtime)) {
266             sleep(100L);
267         }
268 
269         // This ServerSocket implementation ignores the SO_TIMEOUT parameter, force it down.
270         if (waitingForNewAcceptAttempt) {
271             try {
272                 new Socket("127.0.0.1", serverSocket.getLocalPort());
273             } catch (IOException e) {
274             }
275 
276             endtime = System.currentTimeMillis() + (SERVER_SOCKET_TIMEOUT * 2);
277             while (waitingForNewAcceptAttempt && (System.currentTimeMillis() < endtime)) {
278                 sleep(100L);
279             }
280         }
281     }
282 
283     private void sleep(long duration) {
284         try {
285             Thread.sleep(duration);
286         } catch (InterruptedException e) {
287         }
288     }
289 }