package com.limegroup.gnutella; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.InterruptedIOException; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; import junit.framework.Test; import com.bitzi.util.Base32; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PushRequest; import com.limegroup.gnutella.messages.QueryReply; import com.limegroup.gnutella.messages.QueryRequest; import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage; import com.limegroup.gnutella.messages.vendor.PushProxyAcknowledgement; import com.limegroup.gnutella.messages.vendor.PushProxyRequest; import com.limegroup.gnutella.search.HostData; import com.limegroup.gnutella.stubs.ActivityCallbackStub; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.IpPort; import com.limegroup.gnutella.util.IpPortImpl; import com.limegroup.gnutella.util.PrivilegedAccessor; /** * Checks whether (multi)leaves avoid forwarding messages to ultrapeers, do * redirects properly, etc. The test includes a leaf attached to 3 * Ultrapeers. */ public class ClientSidePushProxyTest extends ClientSideTestCase { protected static final int PORT=6669; protected static int TIMEOUT=1000; // should override super public ClientSidePushProxyTest(String name) { super(name); } public static Test suite() { return buildTestSuite(ClientSidePushProxyTest.class); } public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } private static void setAccepted(boolean yes) { try { PrivilegedAccessor.setValue(RouterService.getAcceptor(),"_acceptedIncoming",new Boolean(yes)); }catch(Exception bad) { ErrorService.error(bad); } } ///////////////////////// Actual Tests //////////////////////////// // THIS TEST SHOULD BE RUN FIRST!! public void testPushProxySetup() throws Exception { // send a MessagesSupportedMessage testUP[0].send(MessagesSupportedVendorMessage.instance()); testUP[0].flush(); // we expect to get a PushProxy request Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof PushProxyRequest)) ; // we should answer the push proxy request PushProxyAcknowledgement ack = new PushProxyAcknowledgement(InetAddress.getLocalHost(), 6355, new GUID(m.getGUID())); testUP[0].send(ack); testUP[0].flush(); // client side seems to follow the setup process A-OK } public void testQueryReplyHasProxiesAndCanGIV() throws Exception { setAccepted(false); drain(testUP[0]); // make sure leaf is sharing assertEquals(2, RouterService.getFileManager().getNumFiles()); // send a query that should be answered QueryRequest query = new QueryRequest(GUID.makeGuid(), (byte) 1, "berkeley", null, null, null, null, false, 0, false, 0); testUP[0].send(query); testUP[0].flush(); // await a response Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof QueryReply)) ; // confirm it has proxy info QueryReply reply = (QueryReply) m; assertNotNull(reply.getPushProxies()); // check out PushProxy info Set proxies = reply.getPushProxies(); assertEquals(1, proxies.size()); Iterator iter = proxies.iterator(); IpPort ppi = (IpPort)iter.next(); assertEquals(ppi.getPort(), 6355); assertTrue(ppi.getInetAddress().equals(testUP[0].getInetAddress())); // set up a ServerSocket to get give on ServerSocket ss = new ServerSocket(9000); ss.setReuseAddress(true); ss.setSoTimeout(TIMEOUT); // test that the client responds to a PushRequest PushRequest pr = new PushRequest(GUID.makeGuid(), (byte) 1, RouterService.getMessageRouter()._clientGUID, 0, InetAddress.getLocalHost().getAddress(), 9000); // send the PR off testUP[0].send(pr); testUP[0].flush(); // we should get a incoming GIV Socket givSock = ss.accept(); assertNotNull(givSock); // start reading and confirming the HTTP request String currLine = null; BufferedReader reader = new BufferedReader(new InputStreamReader(givSock.getInputStream())); // confirm a GIV currLine = reader.readLine(); String givLine = "GIV 0:" + (new GUID(RouterService.getMessageRouter()._clientGUID)).toHexString(); assertTrue(currLine.startsWith(givLine)); // everything checks out! givSock.close(); ss.close(); } public void testHTTPRequest() throws Exception { setAccepted(true); drain(testUP[0]); // some setup byte[] clientGUID = GUID.makeGuid(); // construct and send a query byte[] guid = GUID.makeGuid(); RouterService.query(guid, "boalt.org"); // the testUP[0] should get it Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof QueryRequest)) ; // set up a server socket ServerSocket ss = new ServerSocket(7000); ss.setReuseAddress(true); ss.setSoTimeout(25*TIMEOUT); // send a reply with some PushProxy info Set proxies = new TreeSet(IpPort.COMPARATOR); proxies.add(new IpPortImpl("127.0.0.1", 7000)); Response[] res = new Response[1]; res[0] = new Response(10, 10, "boalt.org"); m = new QueryReply(m.getGUID(), (byte) 1, 6355, myIP(), 0, res, clientGUID, new byte[0], true, false, true, true, false, false, proxies); testUP[0].send(m); testUP[0].flush(); // wait a while for Leaf to process result Thread.sleep(2000); assertTrue(((MyActivityCallback)getCallback()).getRFD() != null); // tell the leaf to download the file, should result in push proxy // request RouterService.download((new RemoteFileDesc[] { ((MyActivityCallback)getCallback()).getRFD() }), true, new GUID(m.getGUID())); // wait for the incoming HTTP request Socket httpSock = ss.accept(); assertNotNull(httpSock); // start reading and confirming the HTTP request String currLine = null; BufferedReader reader = new BufferedReader(new InputStreamReader(httpSock.getInputStream())); // confirm a GET/HEAD pushproxy request currLine = reader.readLine(); assertTrue(currLine.startsWith("GET /gnutella/push-proxy") || currLine.startsWith("HEAD /gnutella/push-proxy")); // make sure it sends the correct client GUID int beginIndex = currLine.indexOf("ID=") + 3; String guidString = currLine.substring(beginIndex, beginIndex+26); GUID guidFromBackend = new GUID(clientGUID); GUID guidFromNetwork = new GUID(Base32.decode(guidString)); assertEquals(guidFromNetwork, guidFromBackend); // make sure the node sends the correct X-Node currLine = reader.readLine(); assertTrue(currLine.startsWith("X-Node:")); StringTokenizer st = new StringTokenizer(currLine, ":"); assertEquals(st.nextToken(), "X-Node"); InetAddress addr = InetAddress.getByName(st.nextToken().trim()); Arrays.equals(addr.getAddress(), RouterService.getAddress()); assertEquals(Integer.parseInt(st.nextToken()), PORT); // send back a 202 and make sure no PushRequest is sent via the normal // way BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(httpSock.getOutputStream())); writer.write("HTTP/1.1 202 gobbledygook"); writer.flush(); httpSock.close(); try { do { m = testUP[0].receive(TIMEOUT); assertTrue(!(m instanceof PushRequest)); } while (true) ; } catch (InterruptedIOException expected) {} // now make a connection to the leaf to confirm that it will send a // correct download request Socket push = new Socket(InetAddress.getLocalHost(), PORT); writer = new BufferedWriter(new OutputStreamWriter(push.getOutputStream())); writer.write("GIV 0:" + new GUID(clientGUID).toHexString() + "/\r\n"); writer.write("\r\n"); writer.flush(); reader = new BufferedReader(new InputStreamReader(push.getInputStream())); currLine = reader.readLine(); assertEquals("GET /get/10/boalt.org HTTP/1.1", currLine); // awesome - everything checks out! push.close(); ss.close(); } public void testNoProxiesSendsPushNormal() throws Exception { drain(testUP[0]); // some setup byte[] clientGUID = GUID.makeGuid(); // construct and send a query byte[] guid = GUID.makeGuid(); RouterService.query(guid, "golf is awesome"); // the testUP[0] should get it Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof QueryRequest)) ; // send a reply with NO PushProxy info Response[] res = new Response[1]; res[0] = new Response(10, 10, "golf is awesome"); m = new QueryReply(m.getGUID(), (byte) 1, 6355, myIP(), 0, res, clientGUID, new byte[0], false, false, true, true, false, false, null); testUP[0].send(m); testUP[0].flush(); // wait a while for Leaf to process result Thread.sleep(1000); assertTrue(((MyActivityCallback)getCallback()).getRFD() != null); // tell the leaf to download the file, should result in normal TCP // PushRequest RouterService.download((new RemoteFileDesc[] { ((MyActivityCallback)getCallback()).getRFD() }), true, new GUID(m.getGUID())); // await a PushRequest do { m = testUP[0].receive(25*TIMEOUT); } while (!(m instanceof PushRequest)) ; } public void testCanReactToBadPushProxy() throws Exception { drain(testUP[0]); // some setup byte[] clientGUID = GUID.makeGuid(); // construct and send a query byte[] guid = GUID.makeGuid(); RouterService.query(guid, "berkeley.edu"); // the testUP[0] should get it Message m = null; do { m = testUP[0].receive(TIMEOUT); } while (!(m instanceof QueryRequest)) ; // set up a server socket ServerSocket ss = new ServerSocket(7000); ss.setReuseAddress(true); ss.setSoTimeout(25*TIMEOUT); // send a reply with some BAD PushProxy info //PushProxyInterface[] proxies = new QueryReply.PushProxyContainer[2]; Set proxies = new TreeSet(IpPort.COMPARATOR); proxies.add(new IpPortImpl("127.0.0.1", 7000)); proxies.add(new IpPortImpl("127.0.0.1", 8000)); Response[] res = new Response[1]; res[0] = new Response(10, 10, "berkeley.edu"); m = new QueryReply(m.getGUID(), (byte) 1, 6355, myIP(), 0, res, clientGUID, new byte[0], true, false, true, true, false, false, proxies); testUP[0].send(m); testUP[0].flush(); // wait a while for Leaf to process result Thread.sleep(1000); assertTrue(((MyActivityCallback)getCallback()).getRFD() != null); // tell the leaf to download the file, should result in push proxy // request RouterService.download((new RemoteFileDesc[] { ((MyActivityCallback)getCallback()).getRFD() }), true, new GUID((m.getGUID()))); // wait for the incoming HTTP request Socket httpSock = ss.accept(); assertNotNull(httpSock); // send back a error and make sure the PushRequest is sent via the normal // way BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(httpSock.getOutputStream())); writer.write("HTTP/1.1 410 gobbledygook"); writer.flush(); // there is something going on with timeouts here.... if (CommonUtils.isMacOSX() || CommonUtils.isWindows()) Thread.sleep(300); httpSock.close(); // await a PushRequest do { m = testUP[0].receive(TIMEOUT*8); } while (!(m instanceof PushRequest)) ; // everything checks out ss.close(); } ////////////////////////////////////////////////////////////////// public static Integer numUPs() { return new Integer(1); } public static ActivityCallback getActivityCallback() { return new MyActivityCallback(); } private static byte[] myIP() { return new byte[] { (byte)192, (byte)168, 0, 1 }; } public static class MyActivityCallback extends ActivityCallbackStub { private RemoteFileDesc rfd = null; public RemoteFileDesc getRFD() { return rfd; } public void handleQueryResult(RemoteFileDesc rfd, HostData data, Set locs) { this.rfd = rfd; } } }