package com.limegroup.gnutella.messages.vendor; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; import java.security.Signature; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.zip.Inflater; import junit.framework.Test; import org.limewire.bittorrent.bencoding.Token; import org.limewire.core.settings.FilterSettings; import org.limewire.core.settings.MessageSettings; import org.limewire.gnutella.tests.LimeTestUtils; import org.limewire.inspection.Inspectable; import org.limewire.io.GGEP; import org.limewire.io.GUID; import org.limewire.io.IpPort; import org.limewire.io.IpPortImpl; import org.limewire.security.SecureMessageVerifier; import org.limewire.security.SecureMessageVerifierImpl; import org.limewire.util.ByteUtils; import org.limewire.util.ReadBufferChannel; import org.limewire.util.StringUtils; import com.google.inject.AbstractModule; import com.google.inject.Injector; import com.google.inject.name.Names; import com.limegroup.gnutella.BlockingConnectionUtils; import com.limegroup.gnutella.ConnectionManager; import com.limegroup.gnutella.MessageRouter; import com.limegroup.gnutella.NetworkManager; import com.limegroup.gnutella.ServerSideTestCase; import com.limegroup.gnutella.UDPService; import com.limegroup.gnutella.messages.GGEPKeys; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.MessageFactory; import com.limegroup.gnutella.messages.Message.Network; import com.limegroup.gnutella.messages.vendor.RoutableGGEPMessage.GGEPSigner; public class InspectionTest extends ServerSideTestCase { /** * Ultrapeer 1 UDP connection. */ private DatagramSocket UDP_ACCESS; private MessageFactory messageFactory; private MessagesSupportedVendorMessage messagesSupportedVendorMessage; private MessageRouter messageRouter; private UDPService udpService; private ConnectionManager connectionManager; private NetworkManager networkManager; private KeyPair keyPair; private Injector injector = null; public InspectionTest(String name) { super(name); } public static Test suite() { return buildTestSuite(InspectionTest.class); } @Override public int getNumberOfUltrapeers() { return 1; } @Override public int getNumberOfLeafpeers() { return 2; } @Override public void setSettings() throws Exception { UDP_ACCESS = new DatagramSocket(); UDP_ACCESS.setSoTimeout(1000); FilterSettings.BLACK_LISTED_IP_ADDRESSES.set( new String[] {"*.*.*.*"}); FilterSettings.WHITE_LISTED_IP_ADDRESSES.set( new String[] {InetAddress.getLocalHost().getHostAddress(),"127.*.*.*"}); FilterSettings.INSPECTOR_IP_ADDRESSES.set(new String[]{"127.*.*.*"}); MessageSettings.INSPECTION_VERSION.setValue(0); } @Override public void setUp() throws Exception { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN"); keyGen.initialize(1024, random); keyPair = keyGen.generateKeyPair(); final SecureMessageVerifier smv = new SecureMessageVerifierImpl(keyPair.getPublic(), "testSMV"); injector = LimeTestUtils.createInjector(new AbstractModule() { @Override protected void configure() { bind(SecureMessageVerifier.class).annotatedWith(Names.named("inspection")).toInstance(smv); bind(InspectionTestContainer.class); bind(InspectionTestContainerLazy.class); } }); super.setUp(injector); messageFactory = injector.getInstance(MessageFactory.class); messagesSupportedVendorMessage = injector.getInstance(MessagesSupportedVendorMessage.class); messageRouter = injector.getInstance(MessageRouter.class); udpService = injector.getInstance(UDPService.class); connectionManager = injector.getInstance(ConnectionManager.class); networkManager = injector.getInstance(NetworkManager.class); } private class Signer implements GGEPSigner { public GGEP getSecureGGEP(GGEP original) { try { Signature sig = Signature.getInstance("SHA1withDSA"); sig.initSign(keyPair.getPrivate()); ByteArrayOutputStream out = new ByteArrayOutputStream(); original.write(out); sig.update(out.toByteArray()); byte[] signature = sig.sign(); GGEP ggep = new GGEP(); ggep.put(GGEPKeys.GGEP_HEADER_SECURE_BLOCK); ggep.put(GGEPKeys.GGEP_HEADER_SIGNATURE, signature); return ggep; } catch(Throwable t) { throw new RuntimeException(t); } } } @SuppressWarnings("unchecked") private <T extends Message> T recreate(T req) throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); req.write(out); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); return (T) messageFactory.read(in, Network.TCP); } public void testInspection() throws Exception { InspectionTestContainer container = injector.getInstance(InspectionTestContainer.class); container.setInspectedValue("a"); container.setOtherValue("b"); InspectionRequest request = new InspectionRequestImpl(new Signer(), "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,otherValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,invalidValue"); request = recreate(request); assertEquals(1, request.getRoutableVersion()); assertEquals(false, request.requestsTimeStamp()); assertNull(request.getReturnAddress()); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue", request.getRequestedFields()[0]); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainer,otherValue", request.getRequestedFields()[1]); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainer,invalidValue", request.getRequestedFields()[2]); Map response = tryMessage(request); assertEquals(1, response.size()); assertEquals("a", StringUtils.getASCIIString((byte[])response.get("0"))); request = new InspectionRequestImpl(new GUID(), new Signer(), true, false, -1, 2, null, null, "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,otherValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,invalidValue"); request = recreate(request); assertEquals(2, request.getRoutableVersion()); assertEquals(true, request.requestsTimeStamp()); assertNull(request.getReturnAddress()); response = tryMessage(request); assertEquals(2, response.size()); Thread.sleep(20); long timestamp = Long.valueOf(response.get("-1").toString()); assertLessThan(System.currentTimeMillis(), timestamp); assertGreaterThan(System.currentTimeMillis() - 100, timestamp); } public void testInspectionLazy() throws Exception { InspectionTestContainerLazy container = injector.getInstance(InspectionTestContainerLazy.class); container.setInspectedValue("c"); container.setOtherValue("d"); InspectionRequest request = new InspectionRequestImpl(new Signer(), "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,otherValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,invalidValue"); request = recreate(request); assertEquals(1, request.getRoutableVersion()); assertEquals(false, request.requestsTimeStamp()); assertNull(request.getReturnAddress()); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue", request.getRequestedFields()[0]); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,otherValue", request.getRequestedFields()[1]); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,invalidValue", request.getRequestedFields()[2]); Map response = tryMessage(request); assertEquals(1, response.size()); assertEquals("c", StringUtils.getASCIIString((byte[])response.get("0"))); request = new InspectionRequestImpl(new GUID(), new Signer(), true, false, -1, 2, null, null, "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,otherValue", "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,invalidValue"); request = recreate(request); assertEquals(2, request.getRoutableVersion()); assertEquals(true, request.requestsTimeStamp()); assertNull(request.getReturnAddress()); response = tryMessage(request); assertEquals(2, response.size()); Thread.sleep(20); long timestamp = Long.valueOf(response.get("-1").toString()); assertLessThan(System.currentTimeMillis(), timestamp); assertGreaterThan(System.currentTimeMillis() - 100, timestamp); } public void testEmpty() throws Exception { InspectionRequest request = new InspectionRequestImpl(new Signer(), "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,invalidValue"); request = recreate(request); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainer,invalidValue", request.getRequestedFields()[0]); assertEquals(1, request.getRequestedFields().length); try { tryMessage(request); fail("should not receive anything"); } catch (IOException expected){ } } public void testEmptyLazy() throws Exception { InspectionRequest request = new InspectionRequestImpl(new Signer(), "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,invalidValue"); request = recreate(request); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,invalidValue", request.getRequestedFields()[0]); assertEquals(1, request.getRequestedFields().length); try { tryMessage(request); fail("should not receive anything"); } catch (IOException expected){ } } public void testInvalidSig() throws Exception { InspectionRequestImpl request = new InspectionRequestImpl( new RoutableGGEPMessage.GGEPSigner() { public GGEP getSecureGGEP(GGEP original) { GGEP ret = new GGEP(true); ret.put(GGEPKeys.GGEP_HEADER_SECURE_BLOCK); ret.put(GGEPKeys.GGEP_HEADER_SIGNATURE, StringUtils.toAsciiBytes(" adsf adsf asdf ")); return ret; } }, "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue"); request = recreate(request); try { tryMessage(request); fail("should not receive anything"); }catch(IOException expected){} } public void testInvalidSigLazy() throws Exception { InspectionRequestImpl request = new InspectionRequestImpl( new RoutableGGEPMessage.GGEPSigner() { public GGEP getSecureGGEP(GGEP original) { GGEP ret = new GGEP(true); ret.put(GGEPKeys.GGEP_HEADER_SECURE_BLOCK); ret.put(GGEPKeys.GGEP_HEADER_SIGNATURE, StringUtils.toAsciiBytes(" adsf adsf asdf ")); return ret; } }, "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue"); request = recreate(request); try { tryMessage(request); fail("should not receive anything"); }catch(IOException expected){} } public void testRouting() throws Exception { // one of the leafs supports inspections LEAF[0].send(messagesSupportedVendorMessage); LEAF[0].flush(); // create a request with a return address and one without InspectionRequest notRouted = new InspectionRequestImpl(new Signer(), "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue"); notRouted = recreate(notRouted); InspectionRequest routed = new InspectionRequestImpl(new GUID(), new Signer(), true, false, -1, 2, new IpPortImpl("127.0.0.1", 20000), null, "com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue"); routed = recreate(routed); assertEquals(1, notRouted.getRoutableVersion()); assertNull(notRouted.getReturnAddress()); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue", notRouted.getRequestedFields()[0]); assertEquals(2,routed.getRoutableVersion()); IpPort retAddr = routed.getReturnAddress(); assertEquals(0, IpPort.COMPARATOR.compare(new IpPortImpl("127.0.0.1",20000),retAddr)); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue", routed.getRequestedFields()[0]); // the one without should not be forwarded BlockingConnectionUtils.drainAll(LEAF); tryMessage(notRouted); assertNull(BlockingConnectionUtils.getFirstMessageOfType(LEAF[0], InspectionRequestImpl.class)); assertNull(BlockingConnectionUtils.getFirstMessageOfType(LEAF[1], InspectionRequestImpl.class)); // the one with return address should get answered DatagramSocket socket2 = new DatagramSocket(20000); socket2.setSoTimeout(100); BlockingConnectionUtils.drainAll(LEAF); try { tryMessage(routed); fail("response should have been sent elsewhere"); } catch (IOException expected){} DatagramPacket pack = new DatagramPacket(new byte[1000],1000); socket2.receive(pack); assertEquals(2, MessageSettings.INSPECTION_VERSION.getValue()); // and forwarded to the leaf that supports it. InspectionRequestImpl received = BlockingConnectionUtils.getFirstMessageOfType(LEAF[0], InspectionRequestImpl.class); assertNull(BlockingConnectionUtils.getFirstMessageOfType(LEAF[1], InspectionRequestImpl.class)); assertNotNull(received); // the forwarded message should be identical assertEquals(routed.getGUID(), received.getGUID()); assertEquals(20000,received.getReturnAddress().getPort()); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainer,inspectedValue", received.getRequestedFields()[0]); assertEquals(2, received.getRoutableVersion()); // and handling it should also respond to the return address MessageSettings.INSPECTION_VERSION.setValue(1); MessageRouter router = messageRouter; router.handleMessage(received, connectionManager.getInitializedClientConnections().get(0)); pack = new DatagramPacket(new byte[1000],1000); socket2.receive(pack); assertEquals(2, received.getRoutableVersion()); } public void testRoutingLazy() throws Exception { // one of the leafs supports inspections LEAF[0].send(messagesSupportedVendorMessage); LEAF[0].flush(); // create a request with a return address and one without InspectionRequest notRouted = new InspectionRequestImpl(new Signer(), "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue"); notRouted = recreate(notRouted); InspectionRequest routed = new InspectionRequestImpl(new GUID(), new Signer(), true, false, -1, 2, new IpPortImpl("127.0.0.1", 20000), null, "com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue"); routed = recreate(routed); assertEquals(1, notRouted.getRoutableVersion()); assertNull(notRouted.getReturnAddress()); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue", notRouted.getRequestedFields()[0]); assertEquals(2,routed.getRoutableVersion()); IpPort retAddr = routed.getReturnAddress(); assertEquals(0, IpPort.COMPARATOR.compare(new IpPortImpl("127.0.0.1",20000),retAddr)); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue", routed.getRequestedFields()[0]); // the one without should not be forwarded BlockingConnectionUtils.drainAll(LEAF); tryMessage(notRouted); assertNull(BlockingConnectionUtils.getFirstMessageOfType(LEAF[0], InspectionRequestImpl.class)); assertNull(BlockingConnectionUtils.getFirstMessageOfType(LEAF[1], InspectionRequestImpl.class)); // the one with return address should get answered DatagramSocket socket2 = new DatagramSocket(20000); socket2.setSoTimeout(100); BlockingConnectionUtils.drainAll(LEAF); try { tryMessage(routed); fail("response should have been sent elsewhere"); } catch (IOException expected){} DatagramPacket pack = new DatagramPacket(new byte[1000],1000); socket2.receive(pack); assertEquals(2, MessageSettings.INSPECTION_VERSION.getValue()); // and forwarded to the leaf that supports it. InspectionRequestImpl received = BlockingConnectionUtils.getFirstMessageOfType(LEAF[0], InspectionRequestImpl.class); assertNull(BlockingConnectionUtils.getFirstMessageOfType(LEAF[1], InspectionRequestImpl.class)); assertNotNull(received); // the forwarded message should be identical assertEquals(routed.getGUID(), received.getGUID()); assertEquals(20000,received.getReturnAddress().getPort()); assertEquals("com.limegroup.gnutella.messages.vendor.InspectionTestContainerLazy,inspectedValue", received.getRequestedFields()[0]); assertEquals(2, received.getRoutableVersion()); // and handling it should also respond to the return address MessageSettings.INSPECTION_VERSION.setValue(1); MessageRouter router = messageRouter; router.handleMessage(received, connectionManager.getInitializedClientConnections().get(0)); pack = new DatagramPacket(new byte[1000],1000); socket2.receive(pack); assertEquals(2, received.getRoutableVersion()); } private Map tryMessage(Message m) throws Exception { assertTrue(udpService.isListening()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); m.write(baos); byte [] b = baos.toByteArray(); DatagramPacket pack = new DatagramPacket(b, b.length,InetAddress.getByName("127.0.0.1"),networkManager.getPort()); UDP_ACCESS.send(pack); //now read the response pack = new DatagramPacket(new byte[10000],10000); //not catching IOEx here because not replying is a valid scenario. UDP_ACCESS.receive(pack); byte [] data = pack.getData(); byte [] guid = new byte[16]; System.arraycopy(data,0,guid,0,16); assertEquals(m.getGUID(),guid); assertEquals((byte)0x31,data[16]); byte vendorId[] = new byte[4]; System.arraycopy(data,23,vendorId,0,4); assertEquals(VendorMessage.F_LIME_VENDOR_ID, vendorId); byte [] selectorVersion = new byte[4]; System.arraycopy(data,27,selectorVersion,0,4); //get the selector.... int selector = ByteUtils.ushort2int(ByteUtils.leb2short(selectorVersion, 0)); // get the version.... int version = ByteUtils.ushort2int(ByteUtils.leb2short(selectorVersion, 2)); assertEquals(VendorMessage.F_INSPECTION_RESP, selector); assertEquals(1, version); // inflate the rest Inflater in = new Inflater(); in.setInput(data, 31, data.length - 31); in.finished(); byte [] inflated = new byte[60000]; int numInflated = in.inflate(inflated); return (Map)Token.parse(new ReadBufferChannel(inflated, 0, numInflated)); } } @SuppressWarnings("unchecked") class BEObject implements Inspectable { static BEObject self; @Override public Object inspect() { Map m = new HashMap(); m.put("empty list",new ArrayList()); m.put("some field",5); m.put("wrong type", new BEObject()); return m; } }