package com.limegroup.gnutella;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Random;
import com.limegroup.gnutella.guess.QueryKey;
import com.limegroup.gnutella.guess.QueryKeyGenerator;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PingReply;
import com.limegroup.gnutella.messages.PingRequest;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.util.IOUtils;
/** Simulates a 'network' of unicast enabled clients. The clients don't search,
* but they always respond to queries.
*/
public class UnicastSimulator {
public static final int NUM_LISTENERS = 400;
public static final int PORT_RANGE_BEGIN = 7070;
public static final int GNUTELLA_PORT = 9000;
/**
* Constant for the size of UDP messages to accept -- dependent upon
* IP-layer fragmentation.
*/
private final int BUFFER_SIZE = 8192;
private final int SOCKET_TIMEOUT = 2*1000; // 2 second wait for a message
private PingReply[] _pongs;
private Thread[] _unicasters;
private byte[] _localAddress;
private Thread _tcpListener;
private boolean _shouldRun = true;
private Random rand = new Random();
public UnicastSimulator() throws Exception {
this(InetAddress.getLocalHost().getHostAddress());
}
public UnicastSimulator(String externalAddress) throws Exception {
_localAddress = InetAddress.getByName(externalAddress).getAddress();
// create pings to return...
createPongs();
// create unicast listeners
createListeners();
// create server socket to listen for incoming Gnutella's
createTCPListener(GNUTELLA_PORT);
}
private void createPongs() throws Exception {
_pongs = new PingReply[NUM_LISTENERS];
for (int i = 0; i < NUM_LISTENERS; i++) {
_pongs[i] =
PingReply.createExternal(GUID.makeGuid(), (byte)5,
PORT_RANGE_BEGIN+i, _localAddress,
true);
Assert.that(_pongs[i].isUltrapeer());
}
}
private void createListeners() throws Exception {
_unicasters = new Thread[NUM_LISTENERS];
for (int i = 0; i < NUM_LISTENERS; i++) {
final int offset = i;
_unicasters[i] = new Thread() {
public void run() {
unicastLoop(PORT_RANGE_BEGIN+offset);
}
};
_unicasters[i].start();
}
}
private void createTCPListener(final int port) {
_tcpListener = new Thread() {
public void run() {
tcpLoop(port);
}
};
_tcpListener.start();
}
private boolean shouldRun() {
return _shouldRun;
}
private void tcpLoop(int port) {
ServerSocket servSock = null;
try {
// create a ServerSocket to listen for Gnutellas....
servSock = new ServerSocket(port);
debug("UnicastSimulator.tcpLoop(): listening on port " +
port);
System.out.println("LISTENING FOR GNUTELLA CONNECTIONS ON PORT " +
port);
}
catch (Exception noWork) {
debug("UnicastSimulator.tcpLoop(): couldn't listen on port " +
port);
return;
}
while (shouldRun()) {
try {
// listen for GNUTELLA connections, send back pings when
// established
Socket sock = servSock.accept();
debug("UnicastSimulator.tcpLoop(): got a incoming connection.");
sock.setSoTimeout(Constants.TIMEOUT);
//dont read a word of size more than 8
//("GNUTELLA" is the longest word we know at this time)
String word=IOUtils.readWord(sock.getInputStream(),8);
sock.setSoTimeout(0);
if (word.equals(ConnectionSettings.CONNECT_STRING_FIRST_WORD)) {
Connection conn = new Connection(sock);
conn.initialize(null, null);
debug("UnicastSimulator.tcpLoop(): sending pings.");
for (int i = 0; i < _pongs.length; i++) {
conn.send(_pongs[i]);
Thread.sleep(10);
}
conn.close();
}
}
catch (Exception ignored) {
}
}
try {
servSock.close();
}
catch (Exception ignored) {}
}
/* @param port the port to listen for queries on...
*/
private void unicastLoop(int port) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket(port);
socket.setSoTimeout(1000);
debug("UnicastSimulator.unicastLoop(): listening on port " +
port);
//socket.setSoTimeout(SOCKET_TIMEOUT);
}
catch (SocketException e) {
debug("UnicastSimulator.unicastLoop(): couldn't listen on port " +
port);
return;
}
catch (RuntimeException e) {
debug("UnicastSimulator.unicastLoop(): couldn't listen on port " +
port);
return;
}
byte[] datagramBytes = new byte[BUFFER_SIZE];
DatagramPacket datagram = new DatagramPacket(datagramBytes,
BUFFER_SIZE);
QueryKeyGenerator secretKey = QueryKey.createKeyGenerator();
while (shouldRun()) {
try {
socket.receive(datagram);
byte[] data = datagram.getData();
int length = datagram.getLength();
try {
// construct a message out of it...
InputStream in = new ByteArrayInputStream(data);
Message message = Message.read(in);
if (message == null) continue;
if (message instanceof QueryRequest) {
String query = ((QueryRequest)message).getQuery();
QueryKey queryKey =
((QueryRequest)message).getQueryKey();
QueryKey computed =
QueryKey.getQueryKey(datagram.getAddress(),
datagram.getPort(),
secretKey);
if (!computed.equals(queryKey))
continue; // querykey is invalid!!
byte[] inGUID = ((QueryRequest)message).getGUID();
Response[] resps = new Response[rand.nextInt(15)];
for (int i = 0; i < resps.length; i++) {
resps[i] = new Response(port, 200,
query + " - " +
rand.nextInt(250));
}
QueryReply qr = new QueryReply(inGUID, (byte) 5,
port, _localAddress,
0, resps,
GUID.makeGuid(), false);
// send the QR...
send(socket, qr, datagram.getAddress(),
datagram.getPort());
// also ack with a pong
PingReply toSend = _pongs[port - PORT_RANGE_BEGIN];
send(socket, toSend, datagram.getAddress(),
datagram.getPort());
}
else if (message instanceof PingRequest) {
PingRequest pr = (PingRequest)message;
pr.hop(); // need to hop it!!
if (pr.isQueryKeyRequest()) {
// send a QueryKey back!!!
QueryKey qk =
QueryKey.getQueryKey(datagram.getAddress(),
datagram.getPort(),
secretKey);
PingReply pRep =
PingReply.createQueryKeyReply(pr.getGUID(),
(byte)1,
port,
_localAddress,
2, 2, true, qk);
send(socket, pRep, datagram.getAddress(),
datagram.getPort());
}
else {
PingReply toSend = _pongs[port - PORT_RANGE_BEGIN];
send(socket, toSend, datagram.getAddress(),
datagram.getPort());
}
}
}
catch(BadPacketException e) {
continue;
}
}
catch (InterruptedIOException e) {
continue;
}
catch (IOException e) {
continue;
}
}
debug("UnicastSimulator.unicastLoop(): closing down port " +
port);
socket.close();
}
private void send(DatagramSocket socket, Message msg,
InetAddress ip, int port) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
msg.write(baos);
} catch(IOException e) {
e.printStackTrace();
// can't send the hit, so return
return;
}
byte[] data = baos.toByteArray();
DatagramPacket dg = new DatagramPacket(data, data.length, ip, port);
try {
socket.send(dg);
} catch(IOException e) {
e.printStackTrace();
// not sure what to do here -- try again??
}
}
private final static boolean debugOn = false;
private final static void debug(String out) {
if (debugOn)
System.out.println(out);
}
private final static void debug(Exception out) {
if (debugOn)
out.printStackTrace();
}
public static void main(String argv[]) throws Exception {
if (argv.length > 0) {
UnicastSimulator simulator = new UnicastSimulator(argv[0]);
}
else {
UnicastSimulator simulator = new UnicastSimulator();
}
}
}