1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package net.grinder.tools.tcpproxy;
23
24 import static net.grinder.testutility.SocketUtilities.findFreePort;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.fail;
28 import static org.mockito.Matchers.contains;
29 import static org.mockito.Matchers.isA;
30 import static org.mockito.Mockito.reset;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34
35 import java.io.ByteArrayOutputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStreamWriter;
39 import java.io.PrintWriter;
40 import java.io.StringWriter;
41 import java.net.ServerSocket;
42 import java.net.Socket;
43 import java.net.UnknownHostException;
44
45 import net.grinder.testutility.CallData;
46 import net.grinder.tools.tcpproxy.TCPProxyFilter.FilterException;
47 import net.grinder.util.StreamCopier;
48 import net.grinder.util.TerminalColour;
49
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.mockito.Mock;
53 import org.mockito.MockitoAnnotations;
54 import org.slf4j.Logger;
55
56
57
58
59
60
61
62 public class TestPortForwarderTCPProxyEngine {
63
64 private final MyFilterStubFactory m_requestFilterStubFactory =
65 new MyFilterStubFactory();
66 private final TCPProxyFilter m_requestFilter =
67 m_requestFilterStubFactory.getStub();
68
69 private final MyFilterStubFactory m_responseFilterStubFactory =
70 new MyFilterStubFactory();
71 private final TCPProxyFilter m_responseFilter =
72 m_responseFilterStubFactory.getStub();
73
74 @Mock private Logger m_logger;
75 private final PrintWriter m_out = new PrintWriter(new StringWriter());
76
77 private int m_localPort;
78
79 @Before public void setUp() throws Exception {
80 m_localPort = findFreePort();
81
82 MockitoAnnotations.initMocks(this);
83
84 resetLogger();
85 }
86
87 private void resetLogger() {
88 reset(m_logger);
89 }
90
91 @Test public void testBadLocalPort() throws Exception {
92 final ConnectionDetails badConnectionDetails =
93 new ConnectionDetails(new EndPoint("fictitious-host", 111),
94 new EndPoint("to", 222),
95 false);
96
97 try {
98 new PortForwarderTCPProxyEngine(m_requestFilter,
99 m_responseFilter,
100 m_out,
101 m_logger,
102 badConnectionDetails,
103 false,
104 1000);
105 fail("Expected UnknownHostException");
106 }
107 catch (UnknownHostException e) {
108 }
109 }
110
111 @Test public void testTimeOut() throws Exception {
112
113 final ConnectionDetails connectionDetails =
114 new ConnectionDetails(new EndPoint("localhost", m_localPort),
115 new EndPoint("wherever", 9999),
116 false);
117
118 final TCPProxyEngine engine =
119 new PortForwarderTCPProxyEngine(m_requestFilter,
120 m_responseFilter,
121 m_out,
122 m_logger,
123 connectionDetails,
124 false,
125 10);
126
127 resetLogger();
128
129
130
131
132 engine.run();
133
134 verify(m_logger).error("Listen time out");
135 verifyNoMoreInteractions(m_logger);
136 }
137
138 private void engineTests(AbstractTCPProxyEngine engine,
139 ConnectionDetails connectionDetails)
140 throws Exception {
141
142 final Thread engineThread = new Thread(engine, "Run engine");
143 engineThread.start();
144
145 final Socket clientSocket =
146 new Socket(engine.getListenEndPoint().getHost(),
147 engine.getListenEndPoint().getPort());
148
149 final OutputStreamWriter outputStreamWriter =
150 new OutputStreamWriter(clientSocket.getOutputStream());
151
152 final PrintWriter clientWriter =
153 new PrintWriter(outputStreamWriter, true);
154
155 final String message =
156 "This is some stuff\r\nWe expect to be echoed.\u00ff\u00fe";
157 clientWriter.print(message);
158 clientWriter.flush();
159
160 final InputStream clientInputStream = clientSocket.getInputStream();
161
162 while (clientInputStream.available() <= 0) {
163 Thread.sleep(10);
164 }
165
166 final ByteArrayOutputStream response = new ByteArrayOutputStream();
167
168
169
170 final byte[] buffer = new byte[100];
171
172 while (clientInputStream.available() > 0) {
173 final int bytesRead = clientInputStream.read(buffer, 0, buffer.length);
174 response.write(buffer, 0, bytesRead);
175 }
176
177
178
179
180 assertEquals(message, response.toString(outputStreamWriter.getEncoding()));
181
182 clientSocket.close();
183
184 engine.stop();
185 engineThread.join();
186
187 final CallData callData =
188 m_requestFilterStubFactory.assertSuccess("connectionOpened",
189 ConnectionDetails.class);
190
191
192
193 final ConnectionDetails localConnectionDetails =
194 (ConnectionDetails)callData.getParameters()[0];
195
196 assertEquals(connectionDetails.getRemoteEndPoint(),
197 localConnectionDetails.getRemoteEndPoint());
198 assertEquals(connectionDetails.isSecure(),
199 localConnectionDetails.isSecure());
200
201 m_requestFilterStubFactory.assertSuccess("handle",
202 ConnectionDetails.class,
203 new byte[0].getClass(),
204 Integer.class);
205 m_requestFilterStubFactory.assertSuccess("connectionClosed",
206 ConnectionDetails.class);
207 m_requestFilterStubFactory.assertNoMoreCalls();
208
209 m_responseFilterStubFactory.assertSuccess("connectionOpened",
210 ConnectionDetails.class);
211 m_responseFilterStubFactory.assertSuccess("handle",
212 ConnectionDetails.class,
213 new byte[0].getClass(),
214 Integer.class);
215
216 m_responseFilterStubFactory.setIgnoreCallOrder(true);
217
218 m_responseFilterStubFactory.assertSuccess(
219 "connectionClosed", ConnectionDetails.class);
220 m_responseFilterStubFactory.assertNoMoreCalls();
221
222 verifyNoMoreInteractions(m_logger);
223
224
225 engine.stop();
226
227 m_requestFilterStubFactory.assertNoMoreCalls();
228 m_responseFilterStubFactory.assertNoMoreCalls();
229 }
230
231 @Test public void testEngine() throws Exception {
232
233 final AcceptSingleConnectionAndEcho echoer =
234 new AcceptSingleConnectionAndEcho();
235
236 final EndPoint localEndPoint = new EndPoint("localhost", m_localPort);
237
238 final ConnectionDetails connectionDetails =
239 new ConnectionDetails(localEndPoint,
240 echoer.getEndPoint(),
241 false);
242
243
244 m_requestFilterStubFactory.setResult(null);
245 m_responseFilterStubFactory.setResult(null);
246
247 final AbstractTCPProxyEngine engine =
248 new PortForwarderTCPProxyEngine(m_requestFilter,
249 m_responseFilter,
250 m_out,
251 m_logger,
252 connectionDetails,
253 false,
254 100000);
255
256 m_responseFilterStubFactory.assertNoMoreCalls();
257 m_requestFilterStubFactory.assertNoMoreCalls();
258
259 assertEquals(localEndPoint, engine.getListenEndPoint());
260 assertNotNull(engine.getSocketFactory());
261 m_requestFilterStubFactory.assertIsWrappedBy(engine.getRequestFilter());
262 m_responseFilterStubFactory.assertIsWrappedBy(engine.getResponseFilter());
263 assertEquals(TerminalColour.NONE, engine.getRequestColour());
264 assertEquals(TerminalColour.NONE, engine.getResponseColour());
265
266 resetLogger();
267 m_requestFilterStubFactory.resetCallHistory();
268 m_responseFilterStubFactory.resetCallHistory();
269
270 engineTests(engine, connectionDetails);
271 }
272
273 @Test public void testColourEngine() throws Exception {
274
275 final AcceptSingleConnectionAndEcho echoer =
276 new AcceptSingleConnectionAndEcho();
277
278 final EndPoint localEndPoint = new EndPoint("localhost", m_localPort);
279
280 final ConnectionDetails connectionDetails =
281 new ConnectionDetails(localEndPoint,
282 echoer.getEndPoint(),
283 true);
284
285
286 m_requestFilterStubFactory.setResult(null);
287 m_responseFilterStubFactory.setResult(null);
288
289 final AbstractTCPProxyEngine engine =
290 new PortForwarderTCPProxyEngine(m_requestFilter,
291 m_responseFilter,
292 m_out,
293 m_logger,
294 connectionDetails,
295 true,
296 100000);
297
298 m_responseFilterStubFactory.assertNoMoreCalls();
299 m_requestFilterStubFactory.assertNoMoreCalls();
300
301 assertEquals(localEndPoint, engine.getListenEndPoint());
302 assertNotNull(engine.getSocketFactory());
303 m_requestFilterStubFactory.assertIsWrappedBy(engine.getRequestFilter());
304 m_responseFilterStubFactory.assertIsWrappedBy(engine.getResponseFilter());
305 assertEquals(TerminalColour.RED, engine.getRequestColour());
306 assertEquals(TerminalColour.BLUE, engine.getResponseColour());
307
308 resetLogger();
309 m_requestFilterStubFactory.resetCallHistory();
310 m_responseFilterStubFactory.resetCallHistory();
311
312 engineTests(engine, connectionDetails);
313 }
314
315 @Test public void testOutputStreamFilterTeeWithBadFilters() throws Exception {
316
317 final EndPoint localEndPoint = new EndPoint("localhost", m_localPort);
318
319 final ConnectionDetails connectionDetails =
320 new ConnectionDetails(localEndPoint,
321 new EndPoint("bah", 456),
322 false);
323
324 final AbstractTCPProxyEngine engine =
325 new PortForwarderTCPProxyEngine(m_requestFilter,
326 m_responseFilter,
327 m_out,
328 m_logger,
329 connectionDetails,
330 true,
331 100000);
332
333 final AbstractTCPProxyEngine.OutputStreamFilterTee filterTee =
334 engine.new OutputStreamFilterTee(connectionDetails,
335 new ByteArrayOutputStream(),
336 new BadFilter(),
337 TerminalColour.NONE);
338
339 resetLogger();
340
341 filterTee.connectionOpened();
342 verify(m_logger).error(contains("Problem"), isA(FilterException.class));
343
344 filterTee.connectionClosed();
345 verify(m_logger, times(2))
346 .error(contains("Problem"), isA(FilterException.class));
347
348 filterTee.handle(new byte[0], 0);
349 verify(m_logger, times(3))
350 .error(contains("Problem"), isA(FilterException.class));
351
352 verifyNoMoreInteractions(m_logger);
353 }
354
355 private static final class BadFilter implements TCPProxyFilter {
356
357 public byte[] handle(ConnectionDetails connectionDetails,
358 byte[] buffer,
359 int bytesRead)
360 throws FilterException {
361 throw new FilterException("Problem", null);
362 }
363
364 public void connectionOpened(ConnectionDetails connectionDetails)
365 throws FilterException {
366 throw new FilterException("Problem", null);
367 }
368
369 public void connectionClosed(ConnectionDetails connectionDetails)
370 throws FilterException {
371 throw new FilterException("Problem", null);
372 }
373 }
374
375 private static final class AcceptSingleConnectionAndEcho implements Runnable {
376 private final ServerSocket m_serverSocket;
377
378 public AcceptSingleConnectionAndEcho() throws IOException {
379 m_serverSocket = new ServerSocket(0);
380 new Thread(this, getClass().getName()).start();
381 }
382
383 public EndPoint getEndPoint() {
384 return EndPoint.serverEndPoint(m_serverSocket);
385 }
386
387 public void run() {
388 try {
389 final Socket socket = m_serverSocket.accept();
390
391 new StreamCopier(1000, true).copy(socket.getInputStream(),
392 socket.getOutputStream());
393 }
394 catch (IOException e) {
395 System.err.println("Got a " + e.getMessage());
396 }
397 }
398 }
399 }