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 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.settings.ConnectionSettings;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.udpconnect.SynMessage;
import com.limegroup.gnutella.udpconnect.UDPConnection;
import com.limegroup.gnutella.util.IpPort;
import com.limegroup.gnutella.util.IpPortImpl;
import com.limegroup.gnutella.util.IpPortSet;
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 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 static DatagramSocket UDP_ACCESS;
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());
}
public static void doSettings() {
ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false);
}
public static void globalSetUp() throws Exception {
UDP_ACCESS = new DatagramSocket(9000);
UDP_ACCESS.setSoTimeout(TIMEOUT*2);
}
public void setUp() throws Exception {
// 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...
doSettings();
PrivilegedAccessor.setValue( UDPService.instance(), "_acceptedSolicitedIncoming", new Boolean(true) );
PrivilegedAccessor.setValue( UDPService.instance(), "_acceptedUnsolicitedIncoming", new Boolean(true) );
}
///////////////////////// Actual Tests ////////////////////////////
// THIS TEST SHOULD BE RUN FIRST!!
public void testPushProxySetup() throws Exception {
DatagramPacket pack = null;
// 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
// set up solicted support (see note in setUp())
Thread.sleep(1000);
assertTrue(UDPService.instance().canReceiveSolicited());
}
public void testStartsUDPTransfer() throws Exception {
PrivilegedAccessor.setValue(RouterService.getAcceptor(),
"_externalAddress",
new byte[] {(byte) 10, (byte) 07,
(byte) 19, (byte) 76});
drain(testUP[0]);
drainUDP();
// make sure leaf is sharing
assertEquals(2, RouterService.getFileManager().getNumFiles());
// send a query that should be answered
QueryRequest query = new QueryRequest(GUID.makeGuid(), (byte)2,
0 | QueryRequest.SPECIAL_MINSPEED_MASK |
QueryRequest.SPECIAL_FIREWALL_MASK |
QueryRequest.SPECIAL_FWTRANS_MASK,
"berkeley", "", null,
null, null, false, Message.N_UNKNOWN, 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;
assertEquals(reply.getIP(), "10.7.19.76", reply.getIP());
assertTrue(reply.getSupportsFWTransfer());
assertEquals(UDPConnection.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);
assertTrue(ppi.getInetAddress().equals(
((Connection)rs.getConnectionManager().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 PushRequest(GUID.makeGuid(), (byte) 1,
RouterService.getMessageRouter()._clientGUID,
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 = Message.read(in);
if (syn instanceof SynMessage) break;
}
// but we should NOT get a incoming GIV
try {
Socket givSock = ss.accept();
assertTrue(false);
}
catch (InterruptedIOException expected) {}
ss.close();
}
public void testHTTPRequest() throws Exception {
drain(testUP[0]);
drainUDP();
// 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(15*TIMEOUT);
// send a reply with some PushProxy info
Set proxies = new IpPortSet();
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, 9000, myIP(), 0, res,
clientGUID, new byte[0], true, false, true,
true, false, false, true, proxies);
testUP[0].send(m);
testUP[0].flush();
// wait a while for Leaf to process result
Thread.sleep(1000);
final RemoteFileDesc rfd =((MyActivityCallback)getCallback()).getRFD();
assertNotNull(rfd);
assertNotNull(rfd.getPushAddr());
assertTrue(rfd.needsPush());
// tell the leaf to download the file, should result in push proxy
// request
final GUID fGuid = new GUID(m.getGUID());
Thread runLater = new Thread() {
public void run() {
try {
Thread.sleep(2000);
RouterService.download((new RemoteFileDesc[]{rfd}),
true, fGuid);
}
catch (Exception damn) {
assertTrue(false);
}
}
};
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(addr.getAddress(), RouterService.getExternalAddress());
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();
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 = Message.read(in);
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();
}
//////////////////////////////////////////////////////////////////
public static Integer numUPs() {
return new Integer(1);
}
public static ActivityCallback getActivityCallback() {
return new MyActivityCallback();
}
private static byte[] myIP() {
return new byte[] { (byte)127, (byte)0, 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;
}
}
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
Message syn = Message.read(in);
}
}
}