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.io.OutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; import junit.framework.Test; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PingReply; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.messages.QueryReply; import com.limegroup.gnutella.messages.QueryRequest; import com.limegroup.gnutella.messages.vendor.LimeACKVendorMessage; import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage; import com.limegroup.gnutella.messages.vendor.PushProxyAcknowledgement; import com.limegroup.gnutella.messages.vendor.ReplyNumberVendorMessage; import com.limegroup.gnutella.messages.vendor.UDPConnectBackVendorMessage; import com.limegroup.gnutella.search.QueryHandler; import com.limegroup.gnutella.stubs.ActivityCallbackStub; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.util.PrivilegedAccessor; import com.limegroup.gnutella.util.Sockets; /** * 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; 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()); } private static void doSettings() { ConnectionSettings.DO_NOT_BOOTSTRAP.setValue(true); } ///////////////////////// Actual Tests //////////////////////////// // MUST RUN THIS TEST FIRST public void testBasicProtocol() throws Exception { DatagramPacket pack = null; UDP_ACCESS = new DatagramSocket(); UDP_ACCESS.setSoTimeout(2000); for (int i = 0; i < testUP.length; i++) { assertTrue("should be open", testUP[i].isOpen()); assertTrue("should be up -> leaf", testUP[i].isSupernodeClientConnection()); drain(testUP[i], 500); // OOB client side needs server side leaf guidance testUP[i].send(MessagesSupportedVendorMessage.instance()); testUP[i].flush(); } // first we need to set up GUESS capability // ---------------------------------------- // set up solicited UDP support PrivilegedAccessor.setValue( rs.getUdpService(), "_acceptedSolicitedIncoming", Boolean.TRUE ); // set up unsolicited UDP support PrivilegedAccessor.setValue( rs.getUdpService(), "_acceptedUnsolicitedIncoming", Boolean.TRUE ); // ---------------------------------------- Thread.sleep(250); // we should now be guess capable and tcp incoming capable.... assertTrue(RouterService.isGUESSCapable()); keepAllAlive(testUP); // clear up any messages before we begin the test. drainAll(); // first of all, we should confirm that we are sending out a OOB query. GUID queryGuid = new GUID(RouterService.newQueryGUID()); assertTrue(GUID.addressesMatch(queryGuid.bytes(), RouterService.getAddress(), RouterService.getPort())); RouterService.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 = getFirstQueryRequest(testUP[i]); assertNotNull(qr); assertEquals(new GUID(qr.getGUID()), queryGuid); assertTrue(qr.desiresOutOfBandReplies()); } // now confirm that we follow the OOB protocol ReplyNumberVendorMessage vm = new ReplyNumberVendorMessage(queryGuid, 10); ByteArrayOutputStream baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), RouterService.getPort()); 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 = Message.read(in); if (m instanceof LimeACKVendorMessage) ack = (LimeACKVendorMessage) m; } assertEquals(queryGuid, new GUID(ack.getGUID())); assertEquals(10, ack.getNumResults()); } public void testRemovedQuerySemantics() throws Exception { DatagramPacket pack = null; // 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(RouterService.newQueryGUID()); assertTrue(GUID.addressesMatch(queryGuid.bytes(), RouterService.getAddress(), RouterService.getPort())); RouterService.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 = getFirstQueryRequest(testUP[i]); assertNotNull(qr); assertEquals(new GUID(qr.getGUID()), queryGuid); assertTrue(qr.desiresOutOfBandReplies()); } // now confirm that we follow the OOB protocol ReplyNumberVendorMessage vm = new ReplyNumberVendorMessage(queryGuid, 10); ByteArrayOutputStream baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), RouterService.getPort()); 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 = Message.read(in); if (m instanceof LimeACKVendorMessage) ack = (LimeACKVendorMessage) m; } assertEquals(queryGuid, new GUID(ack.getGUID())); assertEquals(10, ack.getNumResults()); // now stop the query RouterService.stopQuery(queryGuid); keepAllAlive(testUP); drainAll(); // send another ReplyNumber vm = new ReplyNumberVendorMessage(queryGuid, 5); baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), RouterService.getPort()); 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 = Message.read(in); 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. keepAllAlive(testUP); // first of all, we should confirm that we are sending out a OOB query. GUID queryGuid = new GUID(RouterService.newQueryGUID()); assertTrue(GUID.addressesMatch(queryGuid.bytes(), RouterService.getAddress(), RouterService.getPort())); RouterService.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 = getFirstQueryRequest(testUP[i]); assertNotNull(qr); assertEquals(new GUID(qr.getGUID()), queryGuid); assertTrue(qr.desiresOutOfBandReplies()); } // now confirm that we follow the OOB protocol ReplyNumberVendorMessage vm = new ReplyNumberVendorMessage(queryGuid, 10); ByteArrayOutputStream baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), RouterService.getPort()); 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 = Message.read(in); 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] = new Response(10, 10, "susheel"+i+j); Message m = new QueryReply(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 = new ReplyNumberVendorMessage(queryGuid, 5); baos = new ByteArrayOutputStream(); vm.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, testUP[0].getInetAddress(), RouterService.getPort()); 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 = Message.read(in); 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(RouterService.getMessageRouter()._clientGUID)); testUP[2].send(ppAck); testUP[2].flush(); { // this should not go through because of firewall/firewall drain(testUP[0]); QueryRequest query = new QueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_FIREWALL_MASK, "berkeley", "", null, null, null, false, Message.N_UNKNOWN, false, 0, false, 0); testUP[0].send(query);testUP[0].flush(); QueryReply reply = getFirstQueryReply(testUP[0]); assertNull(reply); } { // this should go through because of firewall transfer/solicited drain(testUP[0]); QueryRequest query = new QueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_FIREWALL_MASK | QueryRequest.SPECIAL_FWTRANS_MASK, "susheel", "", null, null, null, false, Message.N_UNKNOWN, false, 0, false, 0); testUP[0].send(query);testUP[0].flush(); QueryReply reply = getFirstQueryReply(testUP[0]); assertTrue(reply.getSupportsFWTransfer()); assertNotNull(reply); } { // this should go through because the source isn't firewalled drain(testUP[1]); QueryRequest query = new QueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_XML_MASK, "susheel", "", null, null, null, false, Message.N_UNKNOWN, false, 0, false, 0); testUP[1].send(query);testUP[1].flush(); QueryReply reply = getFirstQueryReply(testUP[1]); assertFalse(reply.getSupportsFWTransfer()); assertNotNull(reply); } // open up incoming to the test node { Socket sock = null; OutputStream os = null; try { sock=Sockets.connect(InetAddress.getLocalHost().getHostAddress(), SERVER_PORT, 12); os = sock.getOutputStream(); os.write("\n\n".getBytes()); } catch (IOException ignored) { } catch (SecurityException ignored) { } catch (Throwable t) { ErrorService.error(t); } finally { if(sock != null) try { sock.close(); } catch(IOException ignored) {} if(os != null) try { os.close(); } catch(IOException ignored) {} } } Thread.sleep(250); assertTrue(RouterService.acceptedIncomingConnection()); { // this should go through because test node is not firewalled drain(testUP[2]); QueryRequest query = new QueryRequest(GUID.makeGuid(), (byte)2, 0 | QueryRequest.SPECIAL_MINSPEED_MASK | QueryRequest.SPECIAL_FIREWALL_MASK | QueryRequest.SPECIAL_FWTRANS_MASK, "susheel", "", null, null, null, false, Message.N_UNKNOWN, false, 0, false, 0); testUP[2].send(query);testUP[2].flush(); QueryReply reply = getFirstQueryReply(testUP[2]); assertTrue(!reply.getSupportsFWTransfer()); assertNotNull(reply); } } ////////////////////////////////////////////////////////////////// public static Integer numUPs() { return new Integer(3); } public static ActivityCallback getActivityCallback() { return new ActivityCallbackStub(); } private static byte[] myIP() { return new byte[] { (byte)127, (byte)0, 0, 1 }; } }