package com.limegroup.gnutella;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStreamWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.Test;
import org.limewire.core.settings.ConnectionSettings;
import org.limewire.io.GUID;
import org.limewire.io.IpPort;
import org.limewire.io.IpPortImpl;
import org.limewire.io.IpPortSet;
import org.limewire.rudp.RUDPUtils;
import org.limewire.rudp.messages.SynMessage;
import org.limewire.util.Base32;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.limegroup.gnutella.helpers.UrnHelper;
import com.limegroup.gnutella.library.FileManager;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.MessageFactory;
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;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.stubs.NetworkManagerStub;
/**
* Checks whether (multi)leaves avoid forwarding messages to ultrapeers, do
* redirects properly, etc. The test includes a leaf attached to 1 Ultrapeer.
*/
public class ClientSideFirewalledTransferTest extends ClientSideTestCase {
protected static final int PORT=6669;
protected static int TIMEOUT=1000; // should override super
/**
* Ultrapeer 1 UDP connection.
*/
private DatagramSocket UDP_ACCESS;
private NetworkManagerStub networkManagerStub;
private FileManager fileManager;
private QueryRequestFactory queryRequestFactory;
private ConnectionManager connectionManager;
private ApplicationServices applicationServices;
private MessageFactory messageFactory;
private SearchServices searchServices;
private ResponseFactory responseFactory;
private QueryReplyFactory queryReplyFactory;
private MyActivityCallback callback;
private DownloadServices downloadServices;
private Injector injector;
public ClientSideFirewalledTransferTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(ClientSideFirewalledTransferTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
@Override
public void setSettings() {
ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false);
}
@Override
public void setUp() throws Exception {
UDP_ACCESS = new DatagramSocket(9000);
UDP_ACCESS.setSoTimeout(TIMEOUT*2);
networkManagerStub = new NetworkManagerStub();
injector = LimeTestUtils.createInjector(Stage.PRODUCTION, MyActivityCallback.class,
new LimeTestUtils.NetworkManagerStubModule(networkManagerStub));
super.setUp(injector);
fileManager = injector.getInstance(FileManager.class);
queryRequestFactory = injector.getInstance(QueryRequestFactory.class);
connectionManager = injector.getInstance(ConnectionManager.class);
applicationServices = injector.getInstance(ApplicationServices.class);
messageFactory = injector.getInstance(MessageFactory.class);
searchServices = injector.getInstance(SearchServices.class);
responseFactory = injector.getInstance(ResponseFactory.class);
queryReplyFactory = injector.getInstance(QueryReplyFactory.class);
callback = (MyActivityCallback) injector.getInstance(ActivityCallback.class);
downloadServices = injector.getInstance(DownloadServices.class);
// NOTE: UDPService will not change state when a UDP ping or pong is received
// from a conneected host. Therefore, since UDPServiceTest should be testing
// that UDPService functions properly, we will assume that it does and simply
// set the flag saying we can support solicited and unsolicited UDP. This way,
// testing can procede as normal...
networkManagerStub.setCanReceiveSolicited(true);
networkManagerStub.setCanReceiveUnsolicited(true);
networkManagerStub.setCanDoFWT(true);
// has to be false otherwise the client doesn't send fwt results
networkManagerStub.setAcceptedIncomingConnection(false);
networkManagerStub.setExternalAddress(new byte[] {(byte) 10, (byte) 07,
(byte) 19, (byte) 76});
networkManagerStub.setPort(PORT);
exchangePushProxyMesssages();
}
@Override
protected void tearDown() throws Exception {
// important superclass impl has to be called
super.tearDown();
UDP_ACCESS.close();
}
/**
* This is needed to provide the client with a valid push proxy it
* can send along in fwt query replies.
*/
private void exchangePushProxyMesssages() throws Exception {
// send a MessagesSupportedMessage
testUP[0].send(injector.getInstance(MessagesSupportedVendorMessage.class));
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
// set up solicted support (see note in setUp())
}
///////////////////////// Actual Tests ////////////////////////////
public void testStartsUDPTransfer() throws Exception {
BlockingConnectionUtils.drain(testUP[0]);
drainUDP();
// make sure leaf is sharing
assertEquals(2, fileManager.getGnutellaFileList().size());
// send a query that should be answered
QueryRequest query = queryRequestFactory.createQueryRequest(GUID.makeGuid(), (byte)2,
0 | QueryRequest.SPECIAL_MINSPEED_MASK |
QueryRequest.SPECIAL_FIREWALL_MASK |
QueryRequest.SPECIAL_FWTRANS_MASK, "berkeley", "", null, null, false, Network.UNKNOWN,
false, 0, false, 0);
assertTrue(query.canDoFirewalledTransfer());
testUP[0].send(query);
testUP[0].flush();
// await a response
Message m = null;
do {
m = testUP[0].receive(TIMEOUT * 120);
} while (!(m instanceof QueryReply)) ;
// confirm it has proxy info
QueryReply reply = (QueryReply) m;
assertTrue(reply.getSupportsFWTransfer());
assertEquals(RUDPUtils.VERSION, reply.getFWTransferVersion());
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);
assertEquals(ppi.getInetAddress(),
(connectionManager.getConnections().get(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 PushRequestImpl(GUID.makeGuid(), (byte) 1,
applicationServices.getMyGUID(),
PushRequest.FW_TRANS_INDEX,
InetAddress.getLocalHost().getAddress(),
9000);
// send the PR off
testUP[0].send(pr);
testUP[0].flush();
// we should get an incoming UDP transmission
while (true) {
DatagramPacket pack = new DatagramPacket(new byte[1000], 1000);
try {
UDP_ACCESS.receive(pack);
}
catch (IOException bad) {
fail("Did not get SYN", bad);
}
InputStream in = new ByteArrayInputStream(pack.getData());
// as long as we don't get a ClassCastException we are good to go
Message syn = messageFactory.read(in, Network.UDP);
if (syn instanceof SynMessage) break;
}
// but we should NOT get a incoming GIV
try {
ss.accept();
fail("shouldn't have accepted");
} catch (InterruptedIOException expected) {}
ss.close();
}
public void testHTTPRequest() throws Exception {
BlockingConnectionUtils.drain(testUP[0]);
drainUDP();
// 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 = 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(15*TIMEOUT);
// send a reply with some PushProxy info
Set<IpPort> proxies = new IpPortSet();
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, 9000,
myIP(), 0, res, clientGUID, new byte[0], true, false, true,
true, false, false, true, proxies, null);
testUP[0].send(m);
testUP[0].flush();
final RemoteFileDesc rfd = callback.getRFD();
assertNotNull(rfd);
assertTrue(rfd.getAddress() instanceof PushEndpoint);
// tell the leaf to download the file, should result in push proxy
// request
final GUID fGuid = new GUID(m.getGUID());
Thread runLater = new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
downloadServices.download((new RemoteFileDesc[]{rfd}),
true, fGuid);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
};
runLater.start();
// 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 it sends back the correct index
beginIndex = currLine.indexOf("&file=") + 6;
String longString = currLine.substring(beginIndex, beginIndex+10);
long index = Long.parseLong(longString);
assertEquals(index, PushRequest.FW_TRANS_INDEX);
// 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());
assertEquals(networkManagerStub.getExternalAddress(), addr.getAddress());
assertEquals("expecting the stable udp port, since fwt transfer: ", networkManagerStub.getStableUDPPort(), 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();
httpSock.close();
Thread.sleep(500);
// we should get an incoming UDP transmission
while (true) {
DatagramPacket pack = new DatagramPacket(new byte[1000], 1000);
try {
UDP_ACCESS.receive(pack);
}
catch (IOException bad) {
fail("Did not get SYN", bad);
}
InputStream in = new ByteArrayInputStream(pack.getData());
// as long as we don't get a ClassCastException we are good to go
Message syn = messageFactory.read(in, Network.UDP);
if (syn instanceof SynMessage) break;
}
try {
do {
m = testUP[0].receive(TIMEOUT);
assertTrue(!(m instanceof PushRequest));
} while (true) ;
}
catch (InterruptedIOException expected) {}
// awesome - everything checks out!
ss.close();
}
//////////////////////////////////////////////////////////////////
@Override
public int getNumberOfPeers() {
return 1;
}
private static byte[] myIP() {
return new byte[] { (byte)127, (byte)0, 0, 1 };
}
@Singleton
public static class MyActivityCallback extends ActivityCallbackStub {
private volatile RemoteFileDesc rfd = null;
private final CountDownLatch latch = new CountDownLatch(1);
public RemoteFileDesc getRFD() throws InterruptedException {
latch.await(1, TimeUnit.SECONDS);
return rfd;
}
@Override
public void handleQueryResult(RemoteFileDesc rfd,
QueryReply queryReply,
Set locs) {
this.rfd = rfd;
latch.countDown();
}
}
public void drainUDP() throws Exception {
while (true) {
DatagramPacket pack = new DatagramPacket(new byte[1000], 1000);
try {
UDP_ACCESS.receive(pack);
}
catch (InterruptedIOException bad) {
break;
}
InputStream in = new ByteArrayInputStream(pack.getData());
// as long as we don't get a ClassCastException we are good to go
messageFactory.read(in, Network.TCP);
}
}
}