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.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import junit.framework.Test;
import org.limewire.core.settings.ConnectionSettings;
import org.limewire.gnutella.tests.ActivityCallbackStub;
import org.limewire.gnutella.tests.LimeTestUtils;
import org.limewire.gnutella.tests.NetworkManagerStub;
import org.limewire.io.GUID;
import org.limewire.io.IpPort;
import org.limewire.io.IpPortImpl;
import org.limewire.nio.NIOTestUtils;
import org.limewire.nio.ssl.SSLUtils;
import org.limewire.util.Base32;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.limegroup.gnutella.helpers.UrnHelper;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PushRequest;
import com.limegroup.gnutella.messages.PushRequestImpl;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryReplyFactory;
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.MessagesSupportedVendorMessage;
import com.limegroup.gnutella.messages.vendor.PushProxyAcknowledgement;
import com.limegroup.gnutella.messages.vendor.PushProxyRequest;
/**
* 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 final int PORT = 6669;
/**
* static so the activity callback can access it.
*/
protected static int TIMEOUT = 1000; // should override super
@Inject private ConnectionManager connectionManager;
@Inject private QueryRequestFactory queryRequestFactory;
@Inject private ApplicationServices applicationServices;
@Inject private SearchServices searchServices;
@Inject private ResponseFactory responseFactory;
@Inject private QueryReplyFactory queryReplyFactory;
private MyActivityCallback callback;
@Inject private DownloadServices downloadServices;
private NetworkManagerStub networkManagerStub;
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());
}
@Override
public void setUp() throws Exception {
networkManagerStub = new NetworkManagerStub();
networkManagerStub.setPort(PORT);
Injector injector = LimeTestUtils.createInjector(MyActivityCallback.class, new LimeTestUtils.NetworkManagerStubModule(networkManagerStub));
super.setUp(injector);
DownloadManagerImpl downloadManager = (DownloadManagerImpl)injector.getInstance(DownloadManager.class);
callback = (MyActivityCallback) injector.getInstance(ActivityCallback.class);
downloadManager.clearAllDownloads();
// Turn off by default, explicitly test elsewhere.
networkManagerStub.setIncomingTLSEnabled(false);
networkManagerStub.setOutgoingTLSEnabled(false);
// duplicate queries are sent out each time, so avoid the DuplicateFilter
Thread.sleep(2000);
// send a MessagesSupportedMessage
testUP[0].send(injector.getInstance(MessagesSupportedVendorMessage.class));
testUP[0].flush();
// we expect to get a PushProxy request
Message m;
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();
}
public void testQueryReplyHasProxiesAndCanGIVNoTLSSettingOff() throws Exception {
// no tls nor setting, doesn't get TLS connection
doQRPCGTest(false, false, false);
}
public void testQueryReplyHasProxiesAndCanGIVNoTLSSettingOn() throws Exception {
// no tls but setting on, doesn't get TLS connection
doQRPCGTest(false, true, false);
}
public void testQueryReplyHasProxiesAndCanGIVWithTLSNoSetting() throws Exception {
// tls requested, but setting off, doesn't get TLS connection
doQRPCGTest(true, false, false);
}
public void testQueryReplyHasProxiesAndCanGIVWithTLSAndSetting() throws Exception {
// tls requested & setting on -- only time we'll get a TLS connection
doQRPCGTest(true, true, true);
}
private void doQRPCGTest(boolean sendTLS, boolean settingOn, boolean listenTLS) throws Exception {
if(settingOn)
networkManagerStub.setOutgoingTLSEnabled(true);
setAccepted(false);
BlockingConnectionUtils.drain(testUP[0]);
// make sure leaf is sharing
assertEquals(2, gnutellaFileView.size());
assertEquals(1, connectionManager.getNumConnections());
// send a query that should be answered
QueryRequest query = queryRequestFactory.createQueryRequest(GUID.makeGuid(), (byte) 1,
"berkeley", null, null, null, false, Network.UNKNOWN, false, 0);
testUP[0].send(query);
testUP[0].flush();
// await a response
Message m;
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;
if(listenTLS) {
SSLContext context = SSLUtils.getTLSContext();
SSLServerSocket sslServer = (SSLServerSocket)context.getServerSocketFactory().createServerSocket();
sslServer.setNeedClientAuth(false);
sslServer.setWantClientAuth(false);
sslServer.setEnabledCipherSuites(new String[] {"TLS_DH_anon_WITH_AES_128_CBC_SHA"});
ss = sslServer;
} else {
ss = new ServerSocket();
}
try {
ss.setReuseAddress(true);
ss.setSoTimeout(TIMEOUT);
ss.bind(new InetSocketAddress(9000));
// test that the client responds to a PushRequest
PushRequest pr = new PushRequestImpl(GUID.makeGuid(), (byte) 1,
applicationServices.getMyGUID(),
0,
InetAddress.getLocalHost().getAddress(),
9000,
Network.TCP,
sendTLS);
// send the PR off
testUP[0].send(pr);
testUP[0].flush();
// we should get a incoming GIV
Socket givSock = ss.accept();
try {
assertNotNull(givSock);
// start reading and confirming the HTTP request
String currLine;
BufferedReader reader = new BufferedReader(
new InputStreamReader(givSock.getInputStream()));
// confirm a GIV
currLine = reader.readLine();
GUID guid = new GUID(
applicationServices.getMyGUID());
String givLine = "GIV 0:" + guid.toHexString();
assertTrue(currLine.startsWith(givLine));
} finally {
givSock.close();
}
} finally {
ss.close();
}
}
public void testHTTPRequestNoTLS() throws Exception {
doHTTPRequestTest(false, false);
}
public void testHTTPRequestWithTLS() throws Exception {
doHTTPRequestTest(true, true);
}
private void doHTTPRequestTest(boolean settingOn, boolean expectTLS) throws Exception {
if(settingOn)
networkManagerStub.setIncomingTLSEnabled(true);
setAccepted(true);
BlockingConnectionUtils.drain(testUP[0]);
// some setup
byte[] clientGUID = GUID.makeGuid();
// construct and send a query
byte[] guid = GUID.makeGuid();
searchServices.query(guid, "boalt.org");
// the testUP[0] should get it
Message m;
do {
m = testUP[0].receive(TIMEOUT);
} while (!(m instanceof QueryRequest));
// set up a server socket
ServerSocket ss = new ServerSocket(7000);
try {
ss.setReuseAddress(true);
ss.setSoTimeout(25 * TIMEOUT);
// send a reply with some PushProxy info
Set<IpPort> proxies = new TreeSet<IpPort>(IpPort.COMPARATOR);
proxies.add(new IpPortImpl("127.0.0.1", 7000));
Response[] res = new Response[1];
res[0] = responseFactory.createResponse(10, 10, "boalt.org", UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(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
assertNotNull(callback.getRFD());
// tell the leaf to download the file, should result in push proxy
// request
Downloader download = downloadServices.download((new RemoteFileDesc[]
{ callback.getRFD() }), true,
new GUID(m.getGUID()));
// wait for the incoming HTTP request
Socket httpSock = ss.accept();
try {
assertNotNull(httpSock);
// start reading and confirming the HTTP request
String currLine;
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"));
if(expectTLS) {
assertTrue(currLine.contains("tls=true"));
} else {
assertFalse(currLine.contains("tls"));
}
// 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(), networkManagerStub.getAddress());
assertEquals(PORT, Integer.parseInt(st.nextToken()));
// 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();
} finally {
httpSock.close();
}
try {
do {
m = testUP[0].receive(TIMEOUT);
assertTrue(!(m instanceof PushRequest));
} while (true) ;
} catch (InterruptedIOException ignore) {}
// now make a connection to the leaf to confirm that it will send a
// correct download request
Socket push = new Socket(InetAddress.getLocalHost(), PORT);
try {
BufferedWriter writer =
new BufferedWriter(new
OutputStreamWriter(push.getOutputStream()));
writer.write("GIV ");
writer.flush();
NIOTestUtils.waitForNIO();
// the PUSH request is not matched in PushList.getBestHost() if
// this is set to false: the RemoteFileDesc contains the IP
// 192.168.0.1 but since we are connecting from a different IP
// it is not matched but it'll accept it this is set to true and
// both IPs are private
ConnectionSettings.LOCAL_IS_PRIVATE.setValue(true);
writer.write("0:" + new GUID(clientGUID).toHexString() + "/\r\n");
writer.write("\r\n");
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(push.getInputStream()));
String currLine = reader.readLine();
assertEquals(MessageFormat.format("GET /uri-res/N2R?{0} HTTP/1.1", UrnHelper.SHA1), currLine);
} finally {
push.close();
}
download.stop();
} finally {
ss.close();
}
}
public void testNoProxiesSendsPushNormalNoTLS() throws Exception {
doNormalTest(false, false);
}
public void testNoProxiesSendsPushNormalWithTLS() throws Exception {
doNormalTest(true, true);
}
private void doNormalTest(boolean settingOn, boolean expectTLS) throws Exception {
setAccepted(true);
if(settingOn)
networkManagerStub.setIncomingTLSEnabled(true);
BlockingConnectionUtils.drain(testUP[0]);
// some setup
byte[] clientGUID = GUID.makeGuid();
// construct and send a query
byte[] guid = GUID.makeGuid();
searchServices.query(guid, "golf is awesome");
// the testUP[0] should get it
Message m;
do {
m = testUP[0].receive(TIMEOUT);
} while (!(m instanceof QueryRequest));
// send a reply with NO PushProxy info
Response[] res = new Response[1];
res[0] = responseFactory.createResponse(10, 10, "golf is awesome", UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(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
assertNotNull(callback.getRFD());
// tell the leaf to download the file, should result in normal TCP
// PushRequest
Downloader downloader = downloadServices.download(
(new RemoteFileDesc[] { callback.getRFD() }), true, new GUID(m.getGUID()));
// await a PushRequest
do {
m = testUP[0].receive(25 * TIMEOUT);
} while (!(m instanceof PushRequest));
PushRequest pr = (PushRequest)m;
assertNotNull(pr);
assertEquals(expectTLS, pr.isTLSCapable());
assertEquals(clientGUID, pr.getClientGUID());
assertEquals(networkManagerStub.getAddress(), pr.getIP());
assertEquals(networkManagerStub.getPort(), pr.getPort());
assertEquals(10, pr.getIndex());
assertFalse(pr.isFirewallTransferPush());
downloader.stop();
}
public void testCanReactToBadPushProxy() throws Exception {
// assume client accepted connections from the outside successfully
setAccepted(true);
BlockingConnectionUtils.drain(testUP[0]);
// some setup
byte[] clientGUID = GUID.makeGuid();
// construct and send a query
byte[] guid = GUID.makeGuid();
searchServices.query(guid, "berkeley.edu");
// the testUP[0] should get it
Message m;
do {
m = testUP[0].receive(TIMEOUT);
} while (!(m instanceof QueryRequest));
// set up a server socket
ServerSocket ss = new ServerSocket(7000);
try {
ss.setReuseAddress(true);
ss.setSoTimeout(25 * TIMEOUT);
// send a reply with some BAD PushProxy info
// PushProxyInterface[] proxies = new
// QueryReply.PushProxyContainer[2];
Set<IpPort> proxies = new TreeSet<IpPort>(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] = responseFactory.createResponse(10, 10, "berkeley.edu", UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(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
assertNotNull(callback.getRFD());
// tell the leaf to download the file, should result in push proxy
// request
downloadServices
.download(
(new RemoteFileDesc[] { callback.getRFD() }), true, new GUID((m.getGUID())));
// wait for the incoming HTTP request
Socket httpSock = ss.accept();
try {
// send back an 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();
} finally {
httpSock.close();
}
// await a PushRequest
do {
m = testUP[0].receive(TIMEOUT * 8);
} while (!(m instanceof PushRequest));
} finally {
ss.close();
}
}
@Override
public int getNumberOfPeers() {
return 1;
}
private static byte[] myIP() {
return new byte[] { (byte) 192, (byte) 168, 0, 1 };
}
@Singleton
public static class MyActivityCallback extends ActivityCallbackStub {
private Lock rfdLock = new ReentrantLock();
private Condition rfdCondition = rfdLock.newCondition();
private RemoteFileDesc rfd = null;
public RemoteFileDesc getRFD() throws InterruptedException {
rfdLock.lock();
try {
if (rfd == null) {
rfdCondition.await(120, TimeUnit.SECONDS);
}
} finally {
rfdLock.unlock();
}
return rfd;
}
@Override
public void handleQueryResult(RemoteFileDesc rfd, QueryReply queryReply,
Set locs) {
rfdLock.lock();
try {
this.rfd = rfd;
rfdCondition.signal();
} finally {
rfdLock.unlock();
}
}
public void cleanup() {
rfd = null;
}
}
private void setAccepted(boolean accepted) throws Exception {
networkManagerStub.setAcceptedIncomingConnection(accepted);
}
}