package com.limegroup.gnutella; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import junit.framework.Test; import org.limewire.gnutella.tests.LimeTestUtils; import org.limewire.io.GUID; import org.limewire.io.IOUtils; import org.limewire.util.StringUtils; import com.google.inject.Injector; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.MessageFactory; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.messages.Message.Network; import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage; import com.limegroup.gnutella.messages.vendor.TCPConnectBackRedirect; import com.limegroup.gnutella.messages.vendor.UDPConnectBackRedirect; import com.limegroup.gnutella.util.EmptyResponder; /** * Tests that an Ultrapeer correctly handles connect back redirect messages. * * ULTRAPEER[0] ---- CENTRAL TEST ULTRAPEER ---- ULTRAPEER[1] * | * | * | * LEAF * * This test only covers Ultrapeer behavior - leaves don't participate in * server side connect back stuff. */ @SuppressWarnings( { "cast" } ) public final class ServerSideConnectBackRedirectTest extends ServerSideTestCase { /** * Ultrapeer 1 UDP connection. */ private DatagramSocket UDP_ACCESS; /** * Just a TCP connection to use for testing. */ private ServerSocket TCP_ACCESS; /** * The port for TCP_ACCESS */ private final int TCP_ACCESS_PORT = 10776; private MessageFactory messageFactory; public ServerSideConnectBackRedirectTest(String name) { super(name); } public static Test suite() { return buildTestSuite(ServerSideConnectBackRedirectTest.class); } public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } @Override public int getNumberOfUltrapeers() { return 2; } @Override public int getNumberOfLeafpeers() { return 1; } @Override protected void setUp() throws Exception { Injector injector = LimeTestUtils.createInjector(); super.setUp(injector); messageFactory = injector.getInstance(MessageFactory.class); UDP_ACCESS = new DatagramSocket(); TCP_ACCESS = new ServerSocket(TCP_ACCESS_PORT); LEAF[0] = blockingConnectionFactory.createConnection("localhost", PORT); LEAF[0].initialize(headersFactory.createLeafHeaders("localhost"), new EmptyResponder(), 1000); exchangeCapabilities(); } @Override public void tearDown() throws Exception { super.tearDown(); if (TCP_ACCESS != null) { TCP_ACCESS.close(); } } public void exchangeCapabilities() throws Exception { assertTrue("LEAF[0] should be connected", LEAF[0].isOpen()); // Give the connection a chance to send its initial messages Thread.sleep( 1000*2 ); MessagesSupportedVendorMessage msvm = (MessagesSupportedVendorMessage)BlockingConnectionUtils.getFirstMessageOfType(LEAF[0], MessagesSupportedVendorMessage.class, 500); assertNotNull(msvm); assertGreaterThan(0, msvm.supportsTCPConnectBackRedirect()); assertGreaterThan(0, msvm.supportsUDPConnectBackRedirect()); } public void testUDPConnectBackRedirect() throws Exception { drainAll(); GUID cbGuid = new GUID(GUID.makeGuid()); UDPConnectBackRedirect udp = new UDPConnectBackRedirect(cbGuid, InetAddress.getLocalHost(), UDP_ACCESS.getLocalPort()); LEAF[0].send(udp); LEAF[0].flush(); // we should NOT get a ping request over our UDP socket.... UDP_ACCESS.setSoTimeout(1000); DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); fail("got UDP msg"); } catch (IOException good) {} cbGuid = new GUID(GUID.makeGuid()); udp = new UDPConnectBackRedirect(cbGuid, InetAddress.getLocalHost(), UDP_ACCESS.getLocalPort()); ULTRAPEER[0].send(udp); ULTRAPEER[0].flush(); // we should get a ping reply over our UDP socket.... while (true) { pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); Message m = messageFactory.read(in, Network.TCP); if (m instanceof PingRequest) { PingRequest reply = (PingRequest) m; assertEquals(new GUID(reply.getGUID()), cbGuid); break; } } catch (IOException bad) { fail("got IOX", bad); } } } public void testUDPConnectBackAlreadyConnected() throws Exception { drainAll(); DatagramSocket tempSock = new DatagramSocket(LEAF[0].getSocket().getLocalPort()); GUID cbGuid = new GUID(GUID.makeGuid()); UDPConnectBackRedirect udp = new UDPConnectBackRedirect(cbGuid, LEAF[0].getSocket().getInetAddress(), LEAF[0].getSocket().getLocalPort()); ULTRAPEER[0].send(udp); ULTRAPEER[0].flush(); // we should NOT get a ping request over our UDP socket.... tempSock.setSoTimeout(1000); DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); try { tempSock.receive(pack); fail("got UDP msg"); } catch (IOException good) {} tempSock.close(); } public void testTCPTSLConnectBackRedirect() throws Exception { drainAll(); // first attempt with be with TLS TCP_ACCESS.close(); TCP_ACCESS = SSLServerSocketFactory.getDefault().createServerSocket(TCP_ACCESS_PORT); SSLServerSocket sslss = (SSLServerSocket)TCP_ACCESS; sslss.setEnabledCipherSuites(new String[]{"TLS_DH_anon_WITH_AES_128_CBC_SHA"}); TCPConnectBackRedirect tcp = new TCPConnectBackRedirect(InetAddress.getLocalHost(), TCP_ACCESS.getLocalPort()); LEAF[0].send(tcp); LEAF[0].flush(); // we should NOT get a incoming connection TCP_ACCESS.setSoTimeout(1000); try { TCP_ACCESS.accept(); fail("got IOX"); } catch (IOException good) {} tcp = new TCPConnectBackRedirect(InetAddress.getLocalHost(), TCP_ACCESS.getLocalPort()); ULTRAPEER[0].send(tcp); ULTRAPEER[0].flush(); // we should get a incoming connection try { Socket x = TCP_ACCESS.accept(); // just like Acceptor reads words. String word = IOUtils.readLargestWord(x.getInputStream(), 8); assertEquals("CONNECT", word); } catch (IOException bad) { fail("got IOX", bad); } } public void testTCPConnectBackAlreadyConnected() throws Exception { drainAll(); TCPConnectBackRedirect tcp = new TCPConnectBackRedirect(LEAF[0].getSocket().getInetAddress(), TCP_ACCESS.getLocalPort()); ULTRAPEER[0].send(tcp); ULTRAPEER[0].flush(); // we should NOT get an incoming connection TCP_ACCESS.setSoTimeout(1000); try { TCP_ACCESS.accept(); fail("got TCP connection"); } catch (IOException good) {} } public void testConnectBackExpirer() throws Exception { drainAll(); GUID cbGuid = new GUID(GUID.makeGuid()); UDPConnectBackRedirect udp = new UDPConnectBackRedirect(cbGuid, InetAddress.getLocalHost(), UDP_ACCESS.getLocalPort()); ULTRAPEER[1].send(udp); ULTRAPEER[1].flush(); // we should NOT get a ping request over our UDP because we just did this UDP_ACCESS.setSoTimeout(1000); DatagramPacket pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); fail("got UDP msg"); } catch (IOException good) {} TCPConnectBackRedirect tcp = new TCPConnectBackRedirect(InetAddress.getLocalHost(), TCP_ACCESS.getLocalPort()); ULTRAPEER[1].send(tcp); ULTRAPEER[1].flush(); // we should NOT get a incoming connection since we did this already TCP_ACCESS.setSoTimeout(1000); try { TCP_ACCESS.accept(); fail("got TCP connection"); } catch (IOException good) {} // simulate the running of the thread - technically i'm not testing // the situation precisely, but i'm confident the schedule work so the // abstraction isn't terrible Thread cbThread = new Thread(new MessageRouterImpl.ConnectBackExpirer()); cbThread.start(); cbThread.join(); // now these two things should work.... cbGuid = new GUID(GUID.makeGuid()); udp = new UDPConnectBackRedirect(cbGuid, InetAddress.getLocalHost(), UDP_ACCESS.getLocalPort()); ULTRAPEER[1].send(udp); ULTRAPEER[1].flush(); // we should get a ping request over our UDP UDP_ACCESS.setSoTimeout(1000); // we should get a ping reply over our UDP socket.... while (true) { pack = new DatagramPacket(new byte[1000], 1000); try { UDP_ACCESS.receive(pack); InputStream in = new ByteArrayInputStream(pack.getData()); Message m = messageFactory.read(in, Network.TCP); if (m instanceof PingRequest) { PingRequest reply = (PingRequest) m; assertEquals(new GUID(reply.getGUID()), cbGuid); break; } } catch (IOException bad) { fail("got IOX", bad); } } tcp = new TCPConnectBackRedirect(InetAddress.getLocalHost(), TCP_ACCESS.getLocalPort()); ULTRAPEER[1].send(tcp); ULTRAPEER[1].flush(); // we should get a incoming connection TCP_ACCESS.setSoTimeout(1000); try { Socket x = TCP_ACCESS.accept(); byte[] read = new byte["CONNECT BACK\r\n\r\n".length() + 1]; int n = x.getInputStream().read(read); assertGreaterThan(read.length - 1, n); // tls assertFalse((StringUtils.getASCIIString(read).contains("CONNECT BACK"))); // encrypted } catch (IOException bad) { fail("got IOX", bad); } } // ------------------------------------------------------ }