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
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
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 }