/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import gnu.getopt.Getopt;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
* Warning - not necessarily a stable API.
* Used by I2PTunnel CLI only. Consider this sample code.
* Not for use outside this package.
*/
public class I2Ping extends I2PTunnelClientBase {
public static final String PROP_COMMAND = "command";
private static final int PING_COUNT = 3;
private static final int CPING_COUNT = 5;
private static final int PING_TIMEOUT = 30*1000;
private static final long PING_DISTANCE = 1000;
private int MAX_SIMUL_PINGS = 10; // not really final...
private volatile boolean finished;
private final Object simulLock = new Object();
private int simulPings;
private long lastPingTime;
/**
* tunnel.getOptions must contain "command".
* @throws IllegalArgumentException if it doesn't
*/
public I2Ping(Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(-1, ownDest, l, notifyThis, "I2Ping", tunnel);
if (!tunnel.getClientOptions().containsKey(PROP_COMMAND)) {
// todo clean up
throw new IllegalArgumentException("Options does not contain " + PROP_COMMAND);
}
}
/**
* Overrides super. No client ServerSocket is created.
*/
@Override
public void run() {
// Notify constructor that port is ready
synchronized (this) {
listenerReady = true;
notifyAll();
}
l.log("*** I2Ping results:");
try {
runCommand(getTunnel().getClientOptions().getProperty(PROP_COMMAND));
} catch (InterruptedException ex) {
l.log("*** Interrupted");
_log.error("Pinger interrupted", ex);
} catch (IOException ex) {
_log.error("Pinger exception", ex);
}
l.log("*** Finished.");
finished = true;
close(false);
}
public void runCommand(String cmd) throws InterruptedException, IOException {
long timeout = PING_TIMEOUT;
int count = PING_COUNT;
boolean countPing = false;
boolean reportTimes = true;
String hostListFile = null;
int localPort = 0;
int remotePort = 0;
boolean error = false;
String[] argv = DataHelper.split(cmd, " ");
Getopt g = new Getopt("ping", argv, "t:m:n:chl:f:p:");
int c;
while ((c = g.getopt()) != -1) {
switch (c) {
case 't': // timeout
timeout = Long.parseLong(g.getOptarg());
// convenience, convert msec to sec
if (timeout < 100)
timeout *= 1000;
break;
case 'm': // max simultaneous pings
MAX_SIMUL_PINGS = Integer.parseInt(g.getOptarg());
break;
case 'n': // number of pings
count = Integer.parseInt(g.getOptarg());
break;
case 'c': // "count" ping
countPing = true;
count = CPING_COUNT;
break;
case 'h': // ping all hosts
if (hostListFile != null)
error = true;
else
hostListFile = "hosts.txt";
break;
case 'l': // ping a list of hosts
if (hostListFile != null)
error = true;
else
hostListFile = g.getOptarg();
break;
case 'f': // local port
localPort = Integer.parseInt(g.getOptarg());
break;
case 'p': // remote port
remotePort = Integer.parseInt(g.getOptarg());
break;
case '?':
case ':':
default:
error = true;
}
}
int remaining = argv.length - g.getOptind();
if (error ||
remaining > 1 ||
(remaining <= 0 && hostListFile == null) ||
(remaining > 0 && hostListFile != null)) {
System.out.println(usage());
return;
}
if (hostListFile != null) {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(hostListFile), "UTF-8"));
String line;
List<PingHandler> pingHandlers = new ArrayList<PingHandler>();
int i = 0;
while ((line = br.readLine()) != null) {
if (line.startsWith("#")) continue; // comments
if (line.startsWith(";")) continue;
if (line.startsWith("!")) continue;
if (line.indexOf('=') != -1) { // maybe file is hosts.txt?
line = line.substring(0, line.indexOf('='));
}
PingHandler ph = new PingHandler(line, count, localPort, remotePort,
timeout, countPing, reportTimes);
ph.start();
pingHandlers.add(ph);
if (++i > 1)
reportTimes = false;
}
br.close();
for (Thread t : pingHandlers)
t.join();
return;
}
String host = argv[g.getOptind()];
Thread t = new PingHandler(host, count, localPort, remotePort,
timeout, countPing, reportTimes);
t.start();
t.join();
}
/**
* With newlines except for last line
* @since 0.9.12
*/
public static String usage() {
return
"ping <opts> <b64dest|host>\n" +
"ping <opts> -h (pings all hosts in hosts.txt)\n" +
"ping <opts> -l <destlistfile> (pings a list of hosts in a file)\n" +
"Options:\n" +
" -c (require 5 consecutive pings to report success)\n" +
" -m maxSimultaneousPings (default 10)\n" +
" -n numberOfPings (default 3)\n" +
" -t timeout (ms, default 30000)\n" +
" -f fromPort\n" +
" -p toPort";
}
@Override
public boolean close(boolean forced) {
if (!open) return true;
super.close(forced);
if (!forced && !finished) {
l.log("There are still pings running!");
return false;
}
l.log("Closing pinger " + toString());
l.log("Pinger closed.");
return true;
}
private boolean ping(Destination dest, int fromPort, int toPort, long timeout) throws I2PException {
try {
synchronized (simulLock) {
while (simulPings >= MAX_SIMUL_PINGS) {
simulLock.wait();
}
simulPings++;
while (lastPingTime + PING_DISTANCE > System.currentTimeMillis()) {
// no wait here, to delay all pingers
Thread.sleep(PING_DISTANCE / 2);
}
lastPingTime = System.currentTimeMillis();
}
boolean sent = sockMgr.ping(dest, fromPort, toPort, timeout);
synchronized (simulLock) {
simulPings--;
simulLock.notifyAll();
}
return sent;
} catch (InterruptedException ex) {
_log.error("Interrupted", ex);
return false;
}
}
/**
* Does nothing.
* @since 0.9.11
*/
protected void clientConnectionRun(Socket s) {}
private class PingHandler extends I2PAppThread {
private final String destination;
private final int cnt;
private final long timeout;
private final boolean countPing;
private final boolean reportTimes;
private final int localPort;
private final int remotePort;
/**
* As of 0.9.11, does NOT start itself.
* Caller must call start()
* @param dest b64 or b32 or host name
*/
public PingHandler(String dest, int count, int fromPort, int toPort,
long timeout, boolean countPings, boolean report) {
this.destination = dest;
cnt = count;
localPort = fromPort;
remotePort = toPort;
this.timeout = timeout;
countPing = countPings;
reportTimes = report;
setName("PingHandler for " + dest);
}
@Override
public void run() {
try {
Destination dest = lookup(destination);
if (dest == null) {
l.log("Unresolvable: " + destination);
return;
}
int pass = 0;
int fail = 0;
long totalTime = 0;
StringBuilder pingResults = new StringBuilder(2 * cnt + destination.length() + 3);
for (int i = 0; i < cnt; i++) {
boolean sent = ping(dest, localPort, remotePort, timeout);
if (countPing) {
if (!sent) {
pingResults.append(i).append(" ");
break;
} else if (i == cnt - 1) {
pingResults.append("+ ");
}
} else {
if (reportTimes) {
if (sent) {
pass++;
long rtt = System.currentTimeMillis() - lastPingTime;
totalTime += rtt;
l.log((i+1) + ": + " + rtt + " ms");
} else {
fail++;
l.log((i+1) + ": -");
}
} else {
pingResults.append(sent ? "+ " : "- ");
}
}
// System.out.println(sent+" -> "+destination);
}
if (reportTimes) {
pingResults.append(" ").append(pass).append(" received ");
if (pass > 0)
pingResults.append("(average time ").append(totalTime/pass).append(" ms) ");
pingResults.append("and ").append(fail).append(" lost for destination: ");
}
pingResults.append(" ").append(destination);
l.log(pingResults.toString());
} catch (I2PException ex) {
_log.error("Error pinging " + destination, ex);
}
}
/**
* @param name b64 or b32 or host name
* @since 0.9.11
*/
private Destination lookup(String name) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
boolean b32 = name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p");
if (ctx.isRouterContext() && !b32) {
// Local lookup.
// Even though we could do b32 outside router ctx here,
// we do it below instead so we can use the session,
// which we can't do with lookup()
Destination dest = ctx.namingService().lookup(name);
if (dest != null || ctx.isRouterContext() || name.length() >= 516)
return dest;
}
try {
I2PSession sess = sockMgr.getSession();
return sess.lookupDest(name);
} catch (I2PSessionException ise) {
_log.error("Error looking up " + name, ise);
return null;
}
}
}
}