package com.limegroup.gnutella; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import junit.framework.Test; import org.limewire.core.settings.ConnectionSettings; import org.limewire.core.settings.SearchSettings; import org.limewire.gnutella.tests.LimeTestUtils; import org.limewire.gnutella.tests.NetworkManagerStub; import org.limewire.io.GUID; import com.google.inject.Injector; import com.limegroup.gnutella.helpers.UrnHelper; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.MessageFactory; import com.limegroup.gnutella.messages.PingReplyFactory; import com.limegroup.gnutella.messages.QueryReply; import com.limegroup.gnutella.messages.QueryReplyFactory; import com.limegroup.gnutella.messages.QueryRequest; import com.limegroup.gnutella.messages.QueryRequestFactory; import com.limegroup.gnutella.messages.Message.Network; import com.limegroup.gnutella.messages.vendor.LimeACKVendorMessage; import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage; import com.limegroup.gnutella.messages.vendor.OOBProxyControlVendorMessage; import com.limegroup.gnutella.messages.vendor.PushProxyAcknowledgement; import com.limegroup.gnutella.messages.vendor.ReplyNumberVendorMessage; import com.limegroup.gnutella.messages.vendor.ReplyNumberVendorMessageFactory; import com.limegroup.gnutella.search.QueryHandler; import com.limegroup.gnutella.search.SearchResultHandler; /** * Checks whether (multi)leaves avoid forwarding messages to ultrapeers, do * redirects properly, etc. The test includes a leaf attached to 3 * Ultrapeers. */ public class ClientSideOutOfBandReplyTest extends ClientSideTestCase { /** * Ultrapeer 1 UDP connection. */ private static DatagramSocket UDP_ACCESS; private NetworkManagerStub networkManagerStub; private MessagesSupportedVendorMessage messagesSupportedVendorMessage; private SearchServices searchServices; private ResponseFactory responseFactory; private QueryReplyFactory queryReplyFactory; private ReplyNumberVendorMessageFactory replyNumberVendorMessageFactory; private QueryRequestFactory queryRequestFactory; private MessageFactory messageFactory; private PingReplyFactory pingReplyFactory; private SearchResultHandler searchResultHandler; private ApplicationServices applicationServices; public ClientSideOutOfBandReplyTest(String name) { super(name); } public static Test suite() { return buildTestSuite(ClientSideOutOfBandReplyTest.class); } public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } @Override public void setSettings() { ConnectionSettings.DO_NOT_BOOTSTRAP.setValue(true); } @Override protected void setUp() throws Exception { networkManagerStub = new NetworkManagerStub(); Injector injector = LimeTestUtils.createInjector(new LimeTestUtils.NetworkManagerStubModule(networkManagerStub)); super.setUp(injector); messagesSupportedVendorMessage = injector.getInstance(MessagesSupportedVendorMessage.class); searchServices = injector.getInstance(SearchServices.class); responseFactory = injector.getInstance(ResponseFactory.class); queryReplyFactory = injector.getInstance(QueryReplyFactory.class); replyNumberVendorMessageFactory = injector.getInstance(ReplyNumberVendorMessageFactory.class); queryRequestFactory = injector.getInstance(QueryRequestFactory.class); messageFactory = injector.getInstance(MessageFactory.class); pingReplyFactory = injector.getInstance(PingReplyFactory.class); searchResultHandler = injector.getInstance(SearchResultHandler.class); applicationServices = injector.getInstance(ApplicationServices.class); networkManagerStub.setCanReceiveSolicited(true); networkManagerStub.setCanReceiveUnsolicited(true); networkManagerStub.setOOBCapable(true); networkManagerStub.setPort(SERVER_PORT); networkManagerStub.setCanDoFWT(true); networkManagerStub.setExternalAddress(new byte[] { 10, 17, 0, 1 }); UDP_ACCESS = new DatagramSocket(); UDP_ACCESS.setSoTimeout(2000); exchangeMessageSupportAndKeepAlive(); } public void exchangeMessageSupportAndKeepAlive() throws Exception { for (int i = 0; i < testUP.length; i++) { assertTrue("should be open", testUP[i].isOpen()); assertTrue("should be up -> leaf", testUP[i].getConnectionCapabilities().isSupernodeClientConnection()); BlockingConnectionUtils.drain(testUP[i], 500); // OOB client side needs server side leaf guidance testUP[i].send(messagesSupportedVendorMessage); testUP[i].flush(); } // ---------------------------------------- Thread.sleep(250); BlockingConnectionUtils.keepAllAlive(testUP, pingReplyFactory); // clear up any messages before we begin the test. drainAll(); } public void testBasicProtocol() throws Exception { // first of all, we should confirm that we are sending out a OOB query. GUID queryGuid = new GUID(searchServices.newQueryGUID()); assertTrue(GUID.addressesMatch(queryGuid.bytes(), networkManagerStub.getAddress(), networkManagerStub.getPort())); searchServices.query(queryGuid.bytes(), "susheel"); Thread.sleep(250); // all connected UPs should get a OOB query for (int i = 0; i < testUP.length; i++) { QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(testUP[i]); assertNotNull(qr); assertEquals(new GUID(qr.getGUID()), queryGuid); assertTrue(qr.desiresOutOfBandReplies()); } // now confirm that we follow the OOB protocol ReplyNumberVendorMessage vm = replyNumberVendorMessageFactory.create(queryGuid, 10); ByteArrayOutputStream baos = new ByteArrayOutputStream(); vm.write(baos); DatagramPacket pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), SERVER_PORT); UDP_ACCESS.send(pack); // we should get a LimeACK in response LimeACKVendorMessage ack = null; while (ack == null) { pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get ack", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); Message m = messageFactory.read(in, Network.TCP); if (m instanceof LimeACKVendorMessage) ack = (LimeACKVendorMessage) m; } assertEquals(queryGuid, new GUID(ack.getGUID())); assertEquals(10, ack.getNumResults()); assertNotNull(ack.getSecurityToken()); // now send back some results - they should be accepted. Response[] res = new Response[10]; for (int j = 0; j < res.length; j++) res[j] = responseFactory.createResponse(10, 10, "susheel"+j, UrnHelper.SHA1); Message m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false, true, true, false, false, null, ack.getSecurityToken()); baos = new ByteArrayOutputStream(); m.write(baos); byte [] packet = baos.toByteArray(); DatagramPacket reply = new DatagramPacket(packet, packet.length, testUP[0].getInetAddress(), SERVER_PORT); UDP_ACCESS.send(reply); Thread.sleep(250); assertEquals(10,searchResultHandler.getNumResultsForQuery(queryGuid)); } public void testOOBv2Disabled() throws Exception { drainAll(); testUP[0].send(messagesSupportedVendorMessage); testUP[0].flush(); Thread.sleep(200); assertNull(BlockingConnectionUtils.getFirstInstanceOfMessageType(testUP[0], OOBProxyControlVendorMessage.class)); SearchSettings.DISABLE_OOB_V2.setBoolean(true); testUP[0].send(messagesSupportedVendorMessage); testUP[0].flush(); Thread.sleep(2000); OOBProxyControlVendorMessage m = BlockingConnectionUtils.getFirstInstanceOfMessageType(testUP[0], OOBProxyControlVendorMessage.class); assertNotNull(m); assertEquals(2,m.getMaximumDisabledVersion()); } public void testRemovedQuerySemantics() throws Exception { // send a query and make sure that after it is removed (i.e. stopped by // the user) we don't request OOB replies for it // first of all, we should confirm that we are sending out a OOB query. GUID queryGuid = new GUID(searchServices.newQueryGUID()); assertTrue(GUID.addressesMatch(queryGuid.bytes(), networkManagerStub.getAddress(), networkManagerStub.getPort())); searchServices.query(queryGuid.bytes(), "susheel"); Thread.sleep(250); // all connected UPs should get a OOB query for (int i = 0; i < testUP.length; i++) { QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(testUP[i]); assertNotNull(qr); assertEquals(new GUID(qr.getGUID()), queryGuid); assertTrue(qr.desiresOutOfBandReplies()); } // now confirm that we follow the OOB protocol ReplyNumberVendorMessage vm = replyNumberVendorMessageFactory.create(queryGuid, 10); ByteArrayOutputStream baos = new ByteArrayOutputStream(); vm.write(baos); DatagramPacket pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), SERVER_PORT); UDP_ACCESS.send(pack); // we should get a LimeACK in response LimeACKVendorMessage ack = null; while (ack == null) { pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get ack", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); Message m = messageFactory.read(in, Network.TCP); if (m instanceof LimeACKVendorMessage) ack = (LimeACKVendorMessage) m; } assertEquals(queryGuid, new GUID(ack.getGUID())); assertEquals(10, ack.getNumResults()); // now stop the query searchServices.stopQuery(queryGuid); BlockingConnectionUtils.keepAllAlive(testUP, pingReplyFactory); drainAll(); // send another ReplyNumber vm = replyNumberVendorMessageFactory.create(queryGuid, 5); baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), SERVER_PORT); UDP_ACCESS.send(pack); // we should NOT get a LimeACK in response while (true) { pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (InterruptedIOException expected) { break; } catch (IOException bad) { bad.printStackTrace(); } InputStream in = new ByteArrayInputStream(pack.getData()); Message m = messageFactory.read(in, Network.TCP); if (m instanceof LimeACKVendorMessage) assertTrue("we got an ack, weren't supposed to!!", false); } } public void testExpiredQuerySemantics() throws Exception { DatagramPacket pack = null; // send a query and make sure that after it is expired (i.e. enough // results are recieved) we don't request OOB replies for it // clear up messages before we test. BlockingConnectionUtils.keepAllAlive(testUP, pingReplyFactory); // first of all, we should confirm that we are sending out a OOB query. GUID queryGuid = new GUID(searchServices.newQueryGUID()); assertTrue(GUID.addressesMatch(queryGuid.bytes(), networkManagerStub.getAddress(), networkManagerStub.getPort())); searchServices.query(queryGuid.bytes(), "susheel"); Thread.sleep(250); // all connected UPs should get a OOB query for (int i = 0; i < testUP.length; i++) { QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(testUP[i]); assertNotNull(qr); assertEquals(new GUID(qr.getGUID()), queryGuid); assertTrue(qr.desiresOutOfBandReplies()); } // now confirm that we follow the OOB protocol ReplyNumberVendorMessage vm = replyNumberVendorMessageFactory.create(queryGuid, 10); ByteArrayOutputStream baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), SERVER_PORT); UDP_ACCESS.send(pack); // we should get a LimeACK in response LimeACKVendorMessage ack = null; while (ack == null) { pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get ack", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); Message m = messageFactory.read(in, Network.TCP); if (m instanceof LimeACKVendorMessage) ack = (LimeACKVendorMessage) m; } assertEquals(queryGuid, new GUID(ack.getGUID())); assertEquals(10, ack.getNumResults()); // now expire the query by routing hundreds of replies back int respsPerUP = QueryHandler.ULTRAPEER_RESULTS/testUP.length + 5; for (int i = 0; i < testUP.length; i++) { Response[] res = new Response[respsPerUP]; for (int j = 0; j < res.length; j++) res[j] = responseFactory.createResponse(10, 10, "susheel"+i+j, UrnHelper.SHA1); Message m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false, true, true, false, false, null); testUP[i].send(m); testUP[i].flush(); } Thread.sleep(2000); // lets process these results... // send another ReplyNumber vm = replyNumberVendorMessageFactory.create(queryGuid, 5); baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), SERVER_PORT); UDP_ACCESS.send(pack); // we should NOT get a LimeACK in response while (true) { pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (InterruptedIOException expected) { break; } catch (IOException bad) { bad.printStackTrace(); } InputStream in = new ByteArrayInputStream(pack.getData()); Message m = messageFactory.read(in, Network.TCP); if (m instanceof LimeACKVendorMessage) assertTrue("we got an ack, weren't supposed to!!", false); } } public void testFirewalledReplyLogic() throws Exception { // one of the UPs should send a PushProxyAck cuz we don't send the 'FW' // header unless we have proxies PushProxyAcknowledgement ppAck = new PushProxyAcknowledgement(InetAddress.getLocalHost(), testUP[2].getPort(), new GUID(applicationServices.getMyGUID())); testUP[2].send(ppAck); testUP[2].flush(); { // this should not go through because of firewall/firewall BlockingConnectionUtils.drain(testUP[0]); QueryRequest query = queryRequestFactory.createQueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_FIREWALL_MASK, "berkeley", "", null, null, false, Network.UNKNOWN, false, 0, false, 0); testUP[0].send(query);testUP[0].flush(); QueryReply reply = BlockingConnectionUtils.getFirstQueryReply(testUP[0]); assertNull(reply); } { // this should go through because of firewall transfer/solicited BlockingConnectionUtils.drain(testUP[0]); QueryRequest query = queryRequestFactory.createQueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_FIREWALL_MASK | QueryRequest.SPECIAL_FWTRANS_MASK, "susheel", "", null, null, false, Network.UNKNOWN, false, 0, false, 0); assertTrue(query.canDoFirewalledTransfer()); testUP[0].send(query);testUP[0].flush(); QueryReply reply = BlockingConnectionUtils.getFirstQueryReply(testUP[0]); assertNotNull(reply); assertTrue(reply.getSupportsFWTransfer()); } { // this should go through because the source isn't firewalled BlockingConnectionUtils.drain(testUP[1]); QueryRequest query = queryRequestFactory.createQueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_XML_MASK, "susheel", "", null, null, false, Network.UNKNOWN, false, 0, false, 0); testUP[1].send(query);testUP[1].flush(); QueryReply reply = BlockingConnectionUtils.getFirstQueryReply(testUP[1]); assertNotNull(reply); assertFalse(reply.getSupportsFWTransfer()); } // set test client to non-firewalled networkManagerStub.setAcceptedIncomingConnection(true); { // this should go through because test node is not firewalled BlockingConnectionUtils.drain(testUP[2]); QueryRequest query = queryRequestFactory.createQueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_FIREWALL_MASK | QueryRequest.SPECIAL_FWTRANS_MASK, "susheel", "", null, null, false, Network.UNKNOWN, false, 0, false, 0); testUP[2].send(query);testUP[2].flush(); QueryReply reply = BlockingConnectionUtils.getFirstQueryReply(testUP[2]); assertNotNull(reply); assertFalse(reply.getSupportsFWTransfer()); } } public void testNoRNVMSent() throws Exception { SearchSettings.DISABLE_OOB_V2.setBoolean(true); drainAll(); // first of all, we should confirm that we are sending out a OOB query. GUID queryGuid = new GUID(searchServices.newQueryGUID()); assertTrue(GUID.addressesMatch(queryGuid.bytes(), networkManagerStub.getAddress(), networkManagerStub.getPort())); searchServices.query(queryGuid.bytes(), "susheel"); Thread.sleep(250); // all connected UPs should get a OOB query for (int i = 0; i < testUP.length; i++) { QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(testUP[i]); assertNotNull(qr); assertEquals(new GUID(qr.getGUID()), queryGuid); assertTrue(qr.desiresOutOfBandReplies()); } // now, do not send an RNVM and send a reply directly Response[] res = new Response[10]; for (int j = 0; j < res.length; j++) res[j] = responseFactory.createResponse(10, 10, "susheel"+j, UrnHelper.SHA1); Message m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false, true, true, false, false, null); ByteArrayOutputStream baos = new ByteArrayOutputStream(); m.write(baos); byte [] packet = baos.toByteArray(); DatagramPacket reply = new DatagramPacket(packet, packet.length, testUP[0].getInetAddress(), SERVER_PORT); UDP_ACCESS.send(reply); // nothing should be accepted. Thread.sleep(250); SearchResultHandler handler = searchResultHandler; assertEquals(0,handler.getNumResultsForQuery(queryGuid)); } ////////////////////////////////////////////////////////////////// @Override public int getNumberOfPeers() { return 3; } private static byte[] myIP() { return new byte[] { (byte)127, (byte)0, 0, 1 }; } }