package com.limegroup.gnutella.connection; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Properties; import junit.framework.Test; import org.jmock.Expectations; import org.jmock.Mockery; import org.limewire.concurrent.ThreadExecutor; import org.limewire.core.settings.ConnectionSettings; import org.limewire.core.settings.FilterSettings; import org.limewire.core.settings.MessageSettings; import org.limewire.io.GUID; import org.limewire.io.IOUtils; import org.limewire.nio.NIOServerSocket; import org.limewire.nio.observer.AcceptObserver; import org.limewire.service.ErrorService; import com.limegroup.gnutella.BlockingConnectionUtils; import com.limegroup.gnutella.ConnectionManager; import com.limegroup.gnutella.ServerSideTestCase; import com.limegroup.gnutella.StubGnetConnectObserver; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.handshaking.HandshakeResponder; import com.limegroup.gnutella.handshaking.HandshakeResponse; import com.limegroup.gnutella.handshaking.StubHandshakeResponder; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PingReply; import com.limegroup.gnutella.messages.PingReplyFactory; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.messages.PingRequestFactory; import com.limegroup.gnutella.messages.QueryRequest; import com.limegroup.gnutella.messages.QueryRequestFactory; import com.limegroup.gnutella.messages.vendor.CapabilitiesVM; /** * Tests basic connection properties. All tests are done once with compression * and once without. */ public class RoutedConnectionTest extends ServerSideTestCase { private static final int LISTEN_PORT = 12350; private ConnectionManager connectionManager; private PingRequestFactory pingRequestFactory; private PingReplyFactory pingReplyFactory; private QueryRequestFactory queryRequestFactory; private RoutedConnectionFactory routedConnectionFactory; private BlockingConnectionFactory blockingConnectionFactory; public RoutedConnectionTest(String name) { super(name); } public static Test suite() { return buildTestSuite(RoutedConnectionTest.class); } public static void main(String argv[]) { junit.textui.TestRunner.run(suite()); } @Override public int getNumberOfUltrapeers() { return 0; } @Override public int getNumberOfLeafpeers() { return 0; } @Override protected void setUp() throws Exception { super.setUp(); connectionManager = injector.getInstance(ConnectionManager.class); pingRequestFactory = injector.getInstance(PingRequestFactory.class); pingReplyFactory = injector.getInstance(PingReplyFactory.class); queryRequestFactory = injector.getInstance(QueryRequestFactory.class); routedConnectionFactory = injector.getInstance(RoutedConnectionFactory.class); blockingConnectionFactory = injector.getInstance(BlockingConnectionFactory.class); } private Expectations buildCapVMExpectations(final CapabilitiesVM cvm, final boolean tcp, final boolean fwt) { return new Expectations(){{ atLeast(1).of(cvm).canAcceptIncomingTCP(); will(returnValue(tcp)); atLeast(1).of(cvm).canDoFWT(); will(returnValue(fwt)); // capabilities we don't care about ignoring(cvm).isActiveDHTNode(); ignoring(cvm).isPassiveDHTNode(); ignoring(cvm).supportsTLS(); ignoring(cvm).supportsWhatIsNew(); allowing(cvm).supportsSIMPP(); will(returnValue(-1)); ignoring(cvm).isPassiveLeafNode(); ignoring(cvm).supportsFeatureQueries(); allowing(cvm).supportsUpdate(); will(returnValue(-1)); }}; } private Expectations buildQueryExpectations(final QueryRequest query, final boolean tcp, final boolean fwt) throws Exception { return new Expectations(){{ allowing(query).canDoFirewalledTransfer(); will(returnValue(fwt)); allowing(query).isFirewalledSource(); will(returnValue(!tcp)); // stubbed out stuff allowing(query).getFunc(); will(returnValue(Message.F_QUERY)); ignoring(query).isOriginated(); ignoring(query).getHops(); ignoring(query).getCreationTime(); allowing(query).getNetwork(); will(returnValue(Message.Network.TCP)); ignoring(query).getLength(); ignoring(query).getTTL(); }}; } public void testFirewallFirewallDrop() throws Exception { ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, false, false)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, false, false)); mc.handleVendorMessage(cvm); assertFalse(mc.shouldSendQuery(query)); mockery.assertIsSatisfied(); } public void testFirewallFirewallDropDisabled() throws Exception { MessageSettings.ULTRAPEER_FIREWALL_FILTERING.setValue(false); ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, false, false)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, false, false)); mc.handleVendorMessage(cvm); assertTrue(mc.shouldSendQuery(query)); } public void testNonFirewallQuerySend() throws Exception { ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, false, false)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, true, false)); mc.handleVendorMessage(cvm); assertTrue(mc.shouldSendQuery(query)); mockery.assertIsSatisfied(); } public void testNonFirewallLeafSend() throws Exception { ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, true, false)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, false, false)); mc.handleVendorMessage(cvm); assertTrue(mc.shouldSendQuery(query)); mockery.assertIsSatisfied(); } public void testNonFirewallBothSend() throws Exception { ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, true, false)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, true, false)); mc.handleVendorMessage(cvm); assertTrue(mc.shouldSendQuery(query)); mockery.assertIsSatisfied(); } public void testBothFWTSend() throws Exception { ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, false, true)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, false, true)); mc.handleVendorMessage(cvm); assertTrue(mc.shouldSendQuery(query)); mockery.assertIsSatisfied(); } public void testNoFWTLeafDrop() throws Exception { ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, false, true)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, false, false)); mc.handleVendorMessage(cvm); assertFalse(mc.shouldSendQuery(query)); mockery.assertIsSatisfied(); } public void testNoFWTQueryDrop() throws Exception { ConnectionManager cm = connectionManager; BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); GnutellaConnection mc = (GnutellaConnection)cm.getConnections().get(0); Mockery mockery = new Mockery(); final CapabilitiesVM cvm = mockery.mock(CapabilitiesVM.class); mockery.checking(buildCapVMExpectations(cvm, false, false)); final QueryRequest query = mockery.mock(QueryRequest.class); mockery.checking(buildQueryExpectations(query, false, true)); mc.handleVendorMessage(cvm); assertFalse(mc.shouldSendQuery(query)); mockery.assertIsSatisfied(); } /** * Tests the method for checking whether or not a connection is stable. */ public void testIsStable() throws Exception { BlockingConnection conn = createLeafConnection(); Thread.sleep(500); RoutedConnection routedConnection = connectionManager.getConnections().get(0); assertTrue("should not yet be considered stable", !routedConnection.isStable()); Thread.sleep(6000); assertTrue("connection should be considered stable", routedConnection.isStable()); // Keep in mind that Connection is a loopback // connection! That means Acceptor creates a // second ManagedConnection instance for the // incoming connection. Give that instance a // bit time to die or the next test will fail // frequently. conn.close(); Thread.sleep(500); } public void testConnectionStatsRecorded() throws Exception { ConnectionManager cm = connectionManager; assertEquals(0, cm.getNumConnections()); BlockingConnection in = createLeafConnection(); BlockingConnectionUtils.drain(in); assertEquals(1, cm.getNumConnections()); RoutedConnection out = cm.getConnections().get(0); // Record initial msgs. int initialNumSent = out.getConnectionMessageStatistics().getNumMessagesSent(); //long initialBytesSent = out.getUncompressedBytesSent(); long initialBytesRecv = in.getConnectionBandwidthStatistics().getUncompressedBytesReceived(); out.send(pingRequestFactory.createPingRequest((byte)3)); long start = System.currentTimeMillis(); PingRequest pr = (PingRequest)in.receive(); long elapsed = System.currentTimeMillis() - start; assertEquals("unexpected number of sent messages", initialNumSent + 1, out.getConnectionMessageStatistics().getNumMessagesSent()); assertEquals( initialBytesRecv + pr.getTotalLength(), in.getConnectionBandwidthStatistics().getUncompressedBytesReceived() ); // due to delay in updating, this stat is off always. //assertEquals( initialBytesSent + pr.getTotalLength(), out.getUncompressedBytesSent() ); assertLessThan("Unreasonably long send time", 500, elapsed); assertEquals("hopped something other than 0", 0, pr.getHops()); assertEquals("unexpected ttl", 3, pr.getTTL()); out.close(); in.close(); } public void testForwardsGGEP() throws Exception { ConnectionManager cm = connectionManager; assertEquals(0, cm.getNumConnections()); BlockingConnection conn = createLeafConnection(); BlockingConnectionUtils.drain(conn); assertEquals(1, cm.getNumConnections()); RoutedConnection out = cm.getConnections().get(0); out.send(pingReplyFactory.create(GUID.makeGuid(), (byte)1)); Message m = BlockingConnectionUtils.getFirstInstanceOfMessageType(conn, PingReply.class); assertNotNull(m); assertInstanceof("should be a pong", PingReply.class, m); PingReply pr = (PingReply)m; assertTrue("pong should have GGEP", pr.hasGGEPExtension()); assertTrue("should not support unicast", !pr.supportsUnicast()); // not necessary check, and is difficult to mock right. //assertTrue("incorrect daily uptime!", pr.getDailyUptime() > 0); assertTrue("pong should have GGEP", pr.hasGGEPExtension()); out.close(); conn.close(); } /** * Tests the method for checking whether or not the connection is a high- * degree connection that maintains high numbers of intra-Ultrapeer * connections. */ public void testIsHighDegreeConnection() throws Exception { BlockingConnection conn = createLeafConnection(); assertTrue("connection should be high degree", conn.getConnectionCapabilities().isHighDegreeConnection()); conn.close(); } // Tests to make sure that connections are closed correctly from the // client side. public void testClientSideClose() throws Exception { ConnectionManager cm = connectionManager; assertEquals(0, cm.getNumConnections()); BlockingConnection out = createLeafConnection(); BlockingConnectionUtils.drain(out); assertEquals(1, cm.getNumConnections()); RoutedConnection in = cm.getConnections().get(0); //in=acceptor.accept(); assertTrue("connection should be open", out.isOpen()); out.close(); Thread.sleep(100); assertTrue("connection should not be open", !out.isOpen()); Thread.sleep(100); assertTrue(!in.isOpen()); } // Tests to make sure that connections are closed correctly from the // server side. public void testServerSideClose() throws Exception { ConnectionManager cm = connectionManager; assertEquals(0, cm.getNumConnections()); BlockingConnection out = createLeafConnection(); BlockingConnectionUtils.drain(out); assertEquals(1, cm.getNumConnections()); RoutedConnection in = cm.getConnections().get(0); assertTrue("connection should be open", out.isOpen()); out.close(); Message m = pingRequestFactory.createPingRequest((byte)4); m.hop(); in.send(m); Thread.sleep(500); in.send(pingRequestFactory.createPingRequest((byte)4)); Thread.sleep(500); in.send(pingRequestFactory.createPingRequest((byte)4)); Thread.sleep(500); assertTrue("connection should not be open", !in.isOpen()); Thread.sleep(2000); } public void testHashFiltering() throws Exception { URN sha1 = URN.createSHA1Urn("urn:sha1:PLSTHIPQGSSZTS5FJUPAKUZWUGYQYPFB"); QueryRequest urnFile = queryRequestFactory.createQuery(sha1,"java"); GnutellaConnection mc = (GnutellaConnection)routedConnectionFactory.createRoutedConnection("", 1); // default should be no filtering assertFalse(mc.isSpam(urnFile)); // now turn filtering on and rebuild filters FilterSettings.FILTER_HASH_QUERIES.setValue(true); mc = (GnutellaConnection)routedConnectionFactory.createRoutedConnection("", 1); assertTrue(mc.isSpam(urnFile)); FilterSettings.FILTER_HASH_QUERIES.setValue(false); } public void testNonBlockingHandshakeSucceeds() throws Exception { RoutedConnection mc = routedConnectionFactory.createRoutedConnection("127.0.0.1", LISTEN_PORT); ConnectionAcceptor acceptor = new ConnectionAcceptor(); StubGnetConnectObserver observer = new StubGnetConnectObserver(); acceptor.start(); ConnectionSettings.PREFERENCING_ACTIVE.setValue(false); try { mc.initialize(observer); observer.waitForResponse(5000); assertTrue(observer.isConnect()); assertFalse(observer.isBadHandshake()); assertFalse(observer.isNoGOK()); assertFalse(observer.isShutdown()); assertEquals("NIODispatcher", observer.getFinishedThread().getName()); mc.close(); } finally { acceptor.shutdown(); } } public void testNonBlockingBadHandshake() throws Exception { RoutedConnection mc = routedConnectionFactory.createRoutedConnection("127.0.0.1", LISTEN_PORT); ConnectionAcceptor acceptor = new ConnectionAcceptor(); StubGnetConnectObserver observer = new StubGnetConnectObserver(); acceptor.start(); ConnectionSettings.PREFERENCING_ACTIVE.setValue(false); try { acceptor.getObserver().setBadHandshake(true); mc.initialize(observer); observer.waitForResponse(5000); assertFalse(observer.isConnect()); assertTrue(observer.isBadHandshake()); assertFalse(observer.isNoGOK()); assertFalse(observer.isShutdown()); assertEquals("NIODispatcher", observer.getFinishedThread().getName()); mc.close(); } finally { acceptor.shutdown(); } } public void testNonBlockingNGOK() throws Exception { RoutedConnection mc = routedConnectionFactory.createRoutedConnection("127.0.0.1", LISTEN_PORT); ConnectionAcceptor acceptor = new ConnectionAcceptor(); StubGnetConnectObserver observer = new StubGnetConnectObserver(); acceptor.start(); ConnectionSettings.PREFERENCING_ACTIVE.setValue(false); try { acceptor.getObserver().setNoGOK(true); mc.initialize(observer); observer.waitForResponse(5000); assertFalse(observer.isConnect()); assertFalse(observer.isBadHandshake()); assertTrue(observer.isNoGOK()); assertEquals(401, observer.getCode()); assertFalse(observer.isShutdown()); assertEquals("NIODispatcher", observer.getFinishedThread().getName()); mc.close(); } finally { acceptor.shutdown(); } } public void testNonBlockingHandshakeTimeout() throws Exception { RoutedConnection mc = routedConnectionFactory.createRoutedConnection("127.0.0.1", LISTEN_PORT); ConnectionAcceptor acceptor = new ConnectionAcceptor(); StubGnetConnectObserver observer = new StubGnetConnectObserver(); acceptor.start(); ConnectionSettings.PREFERENCING_ACTIVE.setValue(false); try { acceptor.getObserver().setTimeout(true); mc.initialize(observer); observer.waitForResponse(10000); assertFalse(observer.isConnect()); assertFalse(observer.isBadHandshake()); assertFalse(observer.isNoGOK()); assertTrue(observer.isShutdown()); assertEquals("NIODispatcher", observer.getFinishedThread().getName()); mc.close(); } finally { acceptor.shutdown(); } } class EmptyResponder implements HandshakeResponder { public HandshakeResponse respond(HandshakeResponse response, boolean outgoing) { Properties props = new Properties(); return HandshakeResponse.createResponse(props); } public void setLocalePreferencing(boolean b) {} } private class ConnectionAcceptor { private ServerSocket socket; private SimpleAcceptObserver observer; public void start() throws Exception { observer = new SimpleAcceptObserver(); socket = new NIOServerSocket(observer); socket.setReuseAddress(true); socket.bind(new InetSocketAddress(LISTEN_PORT)); } public void shutdown() throws Exception { socket.close(); } public SimpleAcceptObserver getObserver() { return observer; } } private class SimpleAcceptObserver implements AcceptObserver { private boolean noGOK = false; private boolean timeout = false; private boolean badHandshake = false; public void handleIOException(IOException iox) { } public void handleAccept(final Socket socket) throws IOException { ThreadExecutor.startThread(new Runnable() { public void run() { try { if (badHandshake) { socket.close(); return; } if(timeout) { socket.setSoTimeout(60000); socket.getInputStream().read(new byte[2048]); return; } socket.setSoTimeout(3000); InputStream in = socket.getInputStream(); String word = IOUtils.readWord(in, 9); if (!word.equals("GNUTELLA")) throw new IOException("Bad word: " + word); if (noGOK) { socket.getOutputStream().write("GNUTELLA/0.6 401 Failed\r\n\r\n".getBytes()); socket.getOutputStream().flush(); return; } final BlockingConnection con = blockingConnectionFactory.createConnection(socket); con.initialize(null, new StubHandshakeResponder(), 1000); } catch (Exception e) { ErrorService.error(e); } } }, "conninit"); } public void shutdown() { } public void setBadHandshake(boolean badHandshake) { this.badHandshake = badHandshake; } public void setNoGOK(boolean noGOK) { this.noGOK = noGOK; } public void setTimeout(boolean timeout) { this.timeout = timeout; } public void clear() { this.badHandshake = false; this.noGOK = false; this.timeout = false; } } }