package com.limegroup.gnutella; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; import java.util.Arrays; import java.util.Iterator; import java.util.Random; import junit.framework.Test; import org.limewire.core.settings.MessageSettings; import org.limewire.core.settings.SearchSettings; import org.limewire.gnutella.tests.LimeTestUtils; import org.limewire.gnutella.tests.NetworkManagerStub; import org.limewire.io.GUID; import org.limewire.security.AddressSecurityToken; import org.limewire.security.MACCalculatorRepositoryManager; import org.limewire.security.SecurityToken; import org.limewire.util.ByteUtils; import org.limewire.util.FileUtils; import org.limewire.util.PrivilegedAccessor; import org.limewire.util.TestUtils; import com.google.inject.Injector; import com.limegroup.gnutella.helpers.UrnHelper; import com.limegroup.gnutella.messagehandlers.OOBSecurityToken; import com.limegroup.gnutella.messagehandlers.OOBSecurityToken.OOBTokenData; import com.limegroup.gnutella.messages.BadPacketException; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.MessageFactory; import com.limegroup.gnutella.messages.QueryReply; 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.AbstractVendorMessage; import com.limegroup.gnutella.messages.vendor.LimeACKVendorMessage; import com.limegroup.gnutella.messages.vendor.ReplyNumberVendorMessage; import com.limegroup.gnutella.messages.vendor.VendorMessage; import com.limegroup.gnutella.messages.vendor.VendorMessageFactory; import com.limegroup.gnutella.messages.vendor.VendorMessageFactory.VendorMessageParser; import com.limegroup.gnutella.routing.QueryRouteTable; import com.limegroup.gnutella.routing.RouteTableMessage; /** * Tests that an Ultrapeer correctly handles out-of-band queries. Essentially * tests the following methods of MessageRouter: handleQueryRequest, * handleUDPMessage, handleLimeAckMessage, Expirer, QueryBundle, sendQueryReply * * ULTRAPEER_1 ---- CENTRAL TEST ULTRAPEER ---- ULTRAPEER[1] * | * | * | * LEAF[0] * * This test should cover the case for leaves too, since there is no difference * between Leaf and UP when it comes to this behavior. */ @SuppressWarnings("null") public final class ServerSideOutOfBandReplyTest extends ServerSideTestCase { protected static int TIMEOUT = 2000; static String _path = "com/limegroup/gnutella/"; static File _fileWithSOFlag = TestUtils.getResourceFile(_path + "queryWithSOFlag.bin"); static File _fileWithSO = TestUtils.getResourceFile(_path + "queryWithSO.bin"); /** * Ultrapeer 1 UDP connection. */ private volatile static DatagramSocket UDP_ACCESS; private volatile boolean v2disabled; private NetworkManagerStub networkManagerStub; private QueryRequestFactory queryRequestFactory; private MessageFactory messageFactory; private VendorMessageFactory vendorMessageFactory; private MACCalculatorRepositoryManager macManager; public ServerSideOutOfBandReplyTest(String name) { super(name); } public static Test suite() { return buildTestSuite(ServerSideOutOfBandReplyTest.class); } public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } @Override public int getNumberOfUltrapeers() { return 3; } @Override public int getNumberOfLeafpeers() { return 1; } @Override public void setUpQRPTables() throws Exception { QueryRouteTable qrt = new QueryRouteTable(); qrt.add("berkeley"); qrt.add("susheel"); qrt.addIndivisible(UrnHelper.UNIQUE_SHA1.toString()); for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) { LEAF[0].send((RouteTableMessage)iter.next()); LEAF[0].flush(); } // for Ultrapeer 1 qrt = new QueryRouteTable(); qrt.add("leehsus"); qrt.add("berkeley"); for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) { ULTRAPEER[0].send((RouteTableMessage)iter.next()); ULTRAPEER[0].flush(); } } @Override protected void setUp() throws Exception { networkManagerStub = new NetworkManagerStub(); Injector injector = LimeTestUtils.createInjector(new LimeTestUtils.NetworkManagerStubModule(networkManagerStub)); super.setUp(injector); queryRequestFactory = injector.getInstance(QueryRequestFactory.class); messageFactory = injector.getInstance(MessageFactory.class); vendorMessageFactory = injector.getInstance(VendorMessageFactory.class); macManager = injector.getInstance(MACCalculatorRepositoryManager.class); networkManagerStub.setAcceptedIncomingConnection(true); networkManagerStub.setCanReceiveSolicited(true); networkManagerStub.setCanReceiveUnsolicited(true); networkManagerStub.setOOBCapable(true); UDP_ACCESS = new DatagramSocket(10000); } @Override public void tearDown() throws Exception { super.tearDown(); if (UDP_ACCESS != null) { UDP_ACCESS.close(); } } // tests basic out of band functionality // this tests solicited UDP support - it should participate in ACK exchange public void testBasicOutOfBandRequest() throws Exception { drain(); QueryRequest query = queryRequestFactory.createOutOfBandQuery("txt", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); assertTrue(query.desiresOutOfBandReplies()); assertNotEquals(v2disabled, query.desiresOutOfBandRepliesV2()); assertTrue(query.desiresOutOfBandRepliesV3()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[0].send(query); ULTRAPEER[0].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); // we should NOT get a reply to our query assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), query.getGUID())))); } // make sure the GUID is correct assertTrue(Arrays.equals(query.getGUID(), message.getGUID())); ReplyNumberVendorMessage reply = (ReplyNumberVendorMessage) message; assertEquals(2, reply.getNumResults()); // test that we receive new version assertEquals(3, reply.getVersion()); assertTrue(reply.canReceiveUnsolicited()); // make sure we get no further RNVMs try { UDP_ACCESS.setSoTimeout(200); DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack); fail("got something"); } catch (IOException expected){} //rince and repeat, this time pretend to be firewalled query = queryRequestFactory.createOutOfBandQuery("txt", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); assertTrue(query.desiresOutOfBandReplies()); assertNotEquals(v2disabled,query.desiresOutOfBandRepliesV2()); assertTrue(query.desiresOutOfBandRepliesV3()); query.hop(); networkManagerStub.setCanReceiveUnsolicited(false); assertTrue(query.desiresOutOfBandReplies()); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[1].send(query); ULTRAPEER[1].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not message = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); // we should NOT get a reply to our query assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), query.getGUID())))); } // make sure the GUID is correct assertTrue(Arrays.equals(query.getGUID(), message.getGUID())); reply = (ReplyNumberVendorMessage) message; assertEquals(2, reply.getNumResults()); assertFalse(reply.canReceiveUnsolicited()); // test that we receive new version assertEquals(3, reply.getVersion()); //restore our un-firewalled status and repeat query = queryRequestFactory.createOutOfBandQuery("txt", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); query.hop(); networkManagerStub.setCanReceiveUnsolicited(true); ULTRAPEER[2].send(query); ULTRAPEER[2].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not message = null; DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); // we should NOT get a reply to our query assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), query.getGUID())))); } // make sure the GUID is correct assertTrue(Arrays.equals(query.getGUID(), message.getGUID())); reply = (ReplyNumberVendorMessage) message; assertEquals(2, reply.getNumResults()); assertTrue(reply.canReceiveUnsolicited()); // test that we receive new version assertEquals(3, reply.getVersion()); SecurityToken token = new OOBSecurityToken(new OOBTokenData(pack.getAddress(), pack.getPort(), reply.getGUID(), reply.getNumResults()), macManager); // ok - we should ACK the ReplyNumberVM ByteArrayOutputStream baos = new ByteArrayOutputStream(); LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), reply.getNumResults(), token); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); // now we should get TWO replies, each with one response //1) while (!(message instanceof QueryReply)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get reply", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); // should not be receiving redundant RNVMs in this test assertNotInstanceof(ReplyNumberVendorMessage.class, message); } // make sure this is the correct QR assertTrue(Arrays.equals(message.getGUID(), ack.getGUID())); assertEquals(1, ((QueryReply)message).getResultCount()); assertEquals(token.getBytes(), ((QueryReply)message).getSecurityToken()); byte[] receivedTokenBytes = ((QueryReply)message).getSecurityToken(); assertNotNull(receivedTokenBytes); SecurityToken receivedToken = new OOBSecurityToken(receivedTokenBytes, macManager); assertTrue(receivedToken.isFor(new OOBTokenData(pack.getAddress(), pack.getPort(), message.getGUID(), receivedToken.getBytes()[0] & 0xFF))); //2) null out 'message' so we can get the next reply.... message = null; while (!(message instanceof QueryReply)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get reply", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } // make sure this is the correct QR assertTrue(Arrays.equals(message.getGUID(), ack.getGUID())); assertEquals(1, ((QueryReply)message).getResultCount()); // security token is null for old protocol version assertEquals(token.getBytes(), ((QueryReply)message).getSecurityToken()); // make sure that if we send the ACK we don't get another reply - this // is current policy but we may want to change it in the future baos = new ByteArrayOutputStream(); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); // now we should NOT get the reply! try { while (true) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), ack.getGUID())))); } } catch (IOException expected) {} } /** * tests that we send redundant RNVMs if setting is set */ public void testRedundantRNVMs() throws Exception { MessageSettings.OOB_REDUNDANCY.setValue(true); drain(); QueryRequest query = queryRequestFactory.createOutOfBandQuery("txt", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); assertTrue(query.desiresOutOfBandReplies()); assertNotEquals(v2disabled, query.desiresOutOfBandRepliesV2()); assertTrue(query.desiresOutOfBandRepliesV3()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[0].send(query); ULTRAPEER[0].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not int RNVMs = 0; while (RNVMs < 2) { UDP_ACCESS.setSoTimeout(500); DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); Message message = messageFactory.read(in, Network.TCP); // we should get an RNVM assertInstanceof(ReplyNumberVendorMessage.class, message); RNVMs++; } } /** * tests that receiving duplicate ACKs will not result in sending * results twice */ public void testDuplicateACKs() throws Exception { drain(); QueryRequest query = queryRequestFactory.createOutOfBandQuery("txt", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); assertTrue(query.desiresOutOfBandReplies()); assertNotEquals(v2disabled, query.desiresOutOfBandRepliesV2()); assertTrue(query.desiresOutOfBandRepliesV3()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[0].send(query); ULTRAPEER[0].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; DatagramPacket pack = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); // we should NOT get a reply to our query assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), query.getGUID())))); } // prepare an ACK ReplyNumberVendorMessage reply = (ReplyNumberVendorMessage)message; SecurityToken token = new OOBSecurityToken(new OOBTokenData(pack.getAddress(), pack.getPort(), reply.getGUID(), reply.getNumResults()), macManager); ByteArrayOutputStream baos = new ByteArrayOutputStream(); LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), reply.getNumResults(), token); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); // send a duplicate ACK Thread.sleep(50); UDP_ACCESS.send(pack); // now we should get TWO replies, each with one response //1) int replies = 0; try { while (true) { UDP_ACCESS.setSoTimeout(500); DatagramPacket pack2 = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack2); InputStream in = new ByteArrayInputStream(pack2.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); assertInstanceof(QueryReply.class,message); replies++; } } catch (IOException expected) {} assertEquals(2, replies); // send another duplicate ACK UDP_ACCESS.send(pack); // no replies though try { UDP_ACCESS.receive(pack); fail("received something"); } catch (IOException expected){} } public void testBasicOutOfBandRequestV3Only() throws Exception { SearchSettings.DISABLE_OOB_V2.setValue(1); v2disabled = true; testBasicOutOfBandRequest(); SearchSettings.DISABLE_OOB_V2.setValue(0); v2disabled = false; } private void drain() throws Exception { drainAll(); try { UDP_ACCESS.setSoTimeout(10); while(true) { DatagramPacket pack = new DatagramPacket(new byte[1000],1000); UDP_ACCESS.receive(pack); } } catch (IOException iox){} } /** * Test server still handles OOB protocol version 2 for old clients. * * The test is a bit cumbersome in that we have to install an extra * ReplyNumberVendorMessage parser since version 3 discards the old * protocol versions. */ public void testServerHandlesOOBVersion2() throws Exception { drain(); DatagramPacket pack = null; // install extra message parse since old reply number messages are discarded VendorMessageFactory factory = vendorMessageFactory; VendorMessageParser oldParser = factory.getParser(VendorMessage.F_REPLY_NUMBER, VendorMessage.F_LIME_VENDOR_ID); try { factory.setParser(VendorMessage.F_REPLY_NUMBER, VendorMessage.F_LIME_VENDOR_ID, new V2ReplyNumberVendorMessageParser()); // raw message without "SO" key, query = "shusheel" byte[] rawMessage = new byte[] { 127, 0, 1, 1, 11, 71, -79, -94, 4, 47, 27, 72, -81, 112, 7, 0, -128, 6, 0, 15, 0, 0, 0, -60, 0, 115, 117, 115, 104, 101, 101, 108, 0, 117, 114, 110, 58, 0 }; // encode this host's address into message payload System.arraycopy(InetAddress.getLocalHost().getAddress(), 0, rawMessage, 0, 4); ByteUtils.short2leb((short)UDP_ACCESS.getLocalPort(), rawMessage, 13); QueryRequest query = (QueryRequest)messageFactory.read(new ByteArrayInputStream(rawMessage), Network.TCP); assertTrue(query.desiresOutOfBandReplies()); assertTrue(query.desiresOutOfBandRepliesV2()); assertFalse(query.desiresOutOfBandRepliesV3()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[1].send(query); ULTRAPEER[1].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; while (!(message instanceof V2ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } // make sure the GUID is correct assertEquals(query.getGUID(), message.getGUID()); V2ReplyNumberVendorMessage reply = (V2ReplyNumberVendorMessage) message; // since query supports only OOB version 2, reply should be version 2 assertEquals(2, reply.getVersion()); // ok - we should ACK the ReplyNumberVM and NOT get a reply ByteArrayOutputStream baos = new ByteArrayOutputStream(); // send version 2 ack message LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), 1); // we get one result so ask for it ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); while (!(message instanceof QueryReply)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } QueryReply qReply = (QueryReply)message; assertEquals(query.getGUID(), qReply.getGUID()); assertNull(qReply.getSecurityToken()); } finally { factory.setParser(VendorMessage.F_REPLY_NUMBER, VendorMessage.F_LIME_VENDOR_ID, oldParser); } } /** * Test that a message is correctly handled that has both set, * the OOB mask in the minspeed field and the "SO" key in the GGEP. */ public void testSORequestAndMinSpeedOOBMask() throws Exception { drain(); byte[] rawMessage = FileUtils.readFileFully(_fileWithSOFlag); // encode this host's address into message payload System.arraycopy(InetAddress.getLocalHost().getAddress(), 0, rawMessage, 0, 4); ByteUtils.short2leb((short)UDP_ACCESS.getLocalPort(), rawMessage, 13); DatagramPacket pack = null; QueryRequest query = (QueryRequest)messageFactory.read(new ByteArrayInputStream(rawMessage), Network.TCP); assertTrue(query.desiresOutOfBandReplies()); assertTrue(query.desiresOutOfBandRepliesV2()); assertTrue(query.desiresOutOfBandRepliesV3()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[1].send(query); ULTRAPEER[1].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } // make sure the GUID is correct assertEquals(query.getGUID(), message.getGUID()); ReplyNumberVendorMessage reply = (ReplyNumberVendorMessage) message; assertEquals(1, reply.getNumResults()); // since query supports new OOB version, reply should prefer that version assertEquals(3, reply.getVersion()); // ok - we should ACK the ReplyNumberVM and NOT get a reply ByteArrayOutputStream baos = new ByteArrayOutputStream(); SecurityToken token = new AddressSecurityToken(InetAddress.getLocalHost(), 10000, macManager); LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), reply.getNumResults(), token); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); while (!(message instanceof QueryReply)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } QueryReply qReply = (QueryReply)message; assertEquals(query.getGUID(), qReply.getGUID()); assertEquals(token.getBytes(), qReply.getSecurityToken()); } /** * Tests protocol version 3, this is actually done in all other * tests, this test is part of the transition phase when the server * also handled the version 3 , but the client couldn't produce * messages with that version. */ public void testSORequestNoMinSpeedOOBMask() throws Exception { drain(); byte[] rawMessage = FileUtils.readFileFully(_fileWithSO); // enocode System.arraycopy(InetAddress.getLocalHost().getAddress(), 0, rawMessage, 0, 4); ByteUtils.short2leb((short)UDP_ACCESS.getLocalPort(), rawMessage, 13); DatagramPacket pack = null; QueryRequest query = (QueryRequest)messageFactory.read(new ByteArrayInputStream(rawMessage), Network.TCP); assertTrue(query.desiresOutOfBandReplies()); assertFalse(query.desiresOutOfBandRepliesV2()); assertTrue(query.desiresOutOfBandRepliesV3()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[1].send(query); ULTRAPEER[1].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } // make sure the GUID is correct assertEquals(query.getGUID(), message.getGUID()); ReplyNumberVendorMessage reply = (ReplyNumberVendorMessage) message; assertEquals(1, reply.getNumResults()); // query is signals protocol version 3 by setting "SO" key, so server // should respond with version 3 assertEquals(3, reply.getVersion()); // ok - we should ACK the ReplyNumberVM and NOT get a reply ByteArrayOutputStream baos = new ByteArrayOutputStream(); SecurityToken token = new AddressSecurityToken(InetAddress.getLocalHost(), 10000, macManager); LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), reply.getNumResults(), token); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); while (!(message instanceof QueryReply)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } QueryReply qReply = (QueryReply)message; assertEquals(query.getGUID(), qReply.getGUID()); assertEquals(token.getBytes(), qReply.getSecurityToken()); } // makes sure that if we ack back with less results than what the server // has we only get back what we asked for. public void testPiecemealOutOfBandResults() throws Exception { DatagramPacket pack = null; drain(); QueryRequest query = queryRequestFactory.createOutOfBandQuery("txt", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[0].send(query); ULTRAPEER[0].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); // we should NOT get a reply to our query assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), query.getGUID())))); } // make sure the GUID is correct assertTrue(Arrays.equals(query.getGUID(), message.getGUID())); ReplyNumberVendorMessage reply = (ReplyNumberVendorMessage) message; assertEquals(2, reply.getNumResults()); // expect version 3 in replies assertEquals(3, reply.getVersion()); // ok - we should ACK the ReplyNumberVM ByteArrayOutputStream baos = new ByteArrayOutputStream(); OOBSecurityToken token = new OOBSecurityToken(new OOBTokenData(pack.getAddress(), pack.getPort(), reply.getGUID(), 1), macManager); LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), 1, token); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); // now we should get the reply! while (!(message instanceof QueryReply)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get reply", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } // make sure this is the correct QR assertTrue(Arrays.equals(message.getGUID(), ack.getGUID())); assertEquals(1, ((QueryReply)message).getResultCount()); byte[] receivedTokenBytes = ((QueryReply)message).getSecurityToken(); assertNotNull(receivedTokenBytes); SecurityToken receivedToken = new OOBSecurityToken(receivedTokenBytes, macManager); assertTrue(receivedToken.isFor(new OOBTokenData(pack.getAddress(), pack.getPort(), message.getGUID(), receivedToken.getBytes()[0] & 0xFF))); assertEquals(1, receivedToken.getBytes()[0] & 0XFF); // make sure that if we send the ACK we don't get another reply - this // is current policy but we may want to change it in the future baos = new ByteArrayOutputStream(); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); // now we should NOT get the reply! try { while (true) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), ack.getGUID())))); } } catch (IOException expected) {} } // makes sure that if we ACK back with a 0 for the number of results we // don't get a query reply public void testSimpleACKOutOfBandResults() throws Exception { DatagramPacket pack = null; drain(); QueryRequest query = queryRequestFactory.createOutOfBandQuery("txt", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[0].send(query); ULTRAPEER[0].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); // we should NOT get a reply to our query assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), query.getGUID())))); } // make sure the GUID is correct assertTrue(Arrays.equals(query.getGUID(), message.getGUID())); ReplyNumberVendorMessage reply = (ReplyNumberVendorMessage) message; assertEquals(2, reply.getNumResults()); assertEquals(3, reply.getVersion()); // ok - we should ACK the ReplyNumberVM ByteArrayOutputStream baos = new ByteArrayOutputStream(); OOBSecurityToken token = new OOBSecurityToken(new OOBTokenData(pack.getAddress(), pack.getPort(), reply.getGUID(), 0), macManager); // constructor doesn't allow 0 responses anymore, so set 0 manually: LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), 1, token); ((byte[])PrivilegedAccessor.getValue(ack, "_payload"))[0] = 0; ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); // now we should NOT get the reply! try { while (true) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); assertTrue(!((message instanceof QueryReply) && (Arrays.equals(message.getGUID(), ack.getGUID())))); } } catch (IOException expected) {} } // tests that MessageRouter expires GUIDBundles/Replies in a timely fashion // this test requires solicited support public void testExpirer() throws Exception { drain(); DatagramPacket pack = null; // THIS TESTS ASSUMES SOLICITED SUPPORT - set up in a previous test QueryRequest query = queryRequestFactory.createOutOfBandQuery("berkeley", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); query.hop(); // we needed to hop the message because we need to make it seem that it // is from sufficiently far away.... ULTRAPEER[1].send(query); ULTRAPEER[1].flush(); // we should get a ReplyNumberVendorMessage via UDP - we'll get an // interrupted exception if not Message message = null; while (!(message instanceof ReplyNumberVendorMessage)) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); } catch (IOException bad) { fail("Did not get VM", bad); } InputStream in = new ByteArrayInputStream(pack.getData()); // as long as we don't get a ClassCastException we are good to go message = messageFactory.read(in, Network.TCP); } // make sure the GUID is correct assertTrue(Arrays.equals(query.getGUID(), message.getGUID())); ReplyNumberVendorMessage reply = (ReplyNumberVendorMessage) message; assertEquals(1, reply.getNumResults()); assertEquals(3, reply.getVersion()); // WAIT for the expirer to expire the query reply Thread.sleep(60 * 1000); // 1 minute - expirer must run twice // ok - we should ACK the ReplyNumberVM and NOT get a reply ByteArrayOutputStream baos = new ByteArrayOutputStream(); OOBSecurityToken token = new OOBSecurityToken(new OOBTokenData(pack.getAddress(), pack.getPort(), reply.getGUID(), reply.getNumResults()), macManager); LimeACKVendorMessage ack = new LimeACKVendorMessage(new GUID(message.getGUID()), reply.getNumResults(), token); ack.write(baos); pack = new DatagramPacket(baos.toByteArray(), baos.toByteArray().length, pack.getAddress(), pack.getPort()); UDP_ACCESS.send(pack); // now we should NOT get the reply! keep reading until buffer is empty try { while (true) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); message = messageFactory.read(in, Network.TCP); assertTrue(!((message instanceof QueryReply) && (Arrays.equals(ack.getGUID(), message.getGUID())))); } } catch (IOException expected) {} } /** * Ensures that no more than the maximum number of reply bundles are stored at * a single time. */ public void testMaximumOutOfBandResultsBeingStored() throws Exception { DatagramPacket pack = null; drain(); // now lets test that if we send a LOT of out-of-band queries, // we get a lot of ReplyNumberVMs but at the 251st we don't get a // ReplyNumberVM - this test may be fragile because i'm hardcoding // MAX_BUFFERED_REPLIES from MessageRouter final int MAX_BUFFERED_REPLIES = 10; // ok, we need to set MAX_BUFFERED_REPLIES in MessageRouter MessageSettings.MAX_BUFFERED_OOB_REPLIES.setValue(MAX_BUFFERED_REPLIES); // clear stored results from other tests // Map table = messageRouterImpl.getOutOfBandReplies(); // table.clear(); try { // send 10 queries Random rand = new Random(); int numReplyNumberVMs = 0; for (int i = 0; i < MAX_BUFFERED_REPLIES; i++) { QueryRequest query = queryRequestFactory.createOutOfBandQuery((i%2==0) ? "berkeley" : "susheel", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); query.hop(); if (rand.nextInt(2) == 0) { ULTRAPEER[1].send(query); ULTRAPEER[1].flush(); } else { ULTRAPEER[0].send(query); ULTRAPEER[0].flush(); } Thread.sleep(1250); // count 10 ReplyNumberVMs try { while (true) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); Message message = messageFactory.read(in, Network.TCP); if (message instanceof ReplyNumberVendorMessage) numReplyNumberVMs++; } } catch (IOException expected) {} } assertEquals("Didn't get all VMs!!", MAX_BUFFERED_REPLIES, numReplyNumberVMs); // send 2 new queries that shouldn't be ACKed for (int i = 0; i < 2; i++) { QueryRequest query = queryRequestFactory.createOutOfBandQuery((i%2==0) ? "berkeley" : "susheel", InetAddress.getLocalHost().getAddress(), UDP_ACCESS.getLocalPort()); query.hop(); ULTRAPEER[0].send(query); ULTRAPEER[0].flush(); } // should not get any more ReplyNumberVMs try { while (true) { UDP_ACCESS.setSoTimeout(500); pack = new DatagramPacket(new byte[1000], 1000); UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); Message message = messageFactory.read(in, Network.TCP); assertNotInstanceof( ReplyNumberVendorMessage.class, message ); } } catch (IOException expected) {} } finally { // MessageRouter.MAX_BUFFERED_REPLIES = oldMaxBufferedReplies; } } // make sure that the Ultrapeer discards out-of-band query requests that // have an improper address associated with it public void testIdentity() throws Exception { drain(); byte[] crapIP = {(byte)192,(byte)168,(byte)1,(byte)1}; QueryRequest query = queryRequestFactory.createOutOfBandQuery("berkeley", crapIP, 6346); LEAF[0].send(query); LEAF[0].flush(); // ultrapeers should NOT get the QR assertNull(BlockingConnectionUtils.getFirstQueryRequest(ULTRAPEER[0])); assertNull(BlockingConnectionUtils.getFirstQueryRequest(ULTRAPEER[1])); Socket socket = LEAF[0].getSocket(); // try a good query query = queryRequestFactory.createOutOfBandQuery("berkeley", socket.getLocalAddress().getAddress(), 6346); LEAF[0].send(query); LEAF[0].flush(); Thread.sleep(4000); // ultrapeers should get the QR assertNotNull(BlockingConnectionUtils.getFirstQueryRequest(ULTRAPEER[0]) ); assertNotNull(BlockingConnectionUtils.getFirstQueryRequest(ULTRAPEER[1]) ); // LEAF[0] should get the reply assertNotNull(BlockingConnectionUtils.getFirstQueryReply(LEAF[0])); } // a node should NOT send a reply out of band via UDP if it is not // far away (low hop) public void testLowHopOutOfBandRequest() throws Exception { drain(); byte[] meIP = {(byte)127,(byte)0,(byte)0,(byte)1}; QueryRequest query = queryRequestFactory.createOutOfBandQuery("susheel", meIP, UDP_ACCESS.getLocalPort()); ULTRAPEER[1].send(query); ULTRAPEER[1].flush(); // ULTRAPEER[1] should get a reply via TCP assertNotNull(BlockingConnectionUtils.getFirstQueryReply(ULTRAPEER[1])); } private static class V2ReplyNumberVendorMessageParser implements VendorMessageParser { public VendorMessage parse(byte[] guid, byte ttl, byte hops, int version, byte[] restOf, Network network) throws BadPacketException { return new V2ReplyNumberVendorMessage(guid, ttl, hops, version, restOf, network); } } private static class V2ReplyNumberVendorMessage extends AbstractVendorMessage { public V2ReplyNumberVendorMessage(byte[] guid, byte ttl, byte hops, int version, byte[] payload, Network network) throws BadPacketException { super(guid, ttl, hops, F_LIME_VENDOR_ID, F_REPLY_NUMBER, version, payload, network); } } }