/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.command.net;
import java.io.PrintWriter;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.jnode.net.SocketBuffer;
import org.jnode.net.ethernet.EthernetConstants;
import org.jnode.net.ipv4.IPv4Address;
import org.jnode.net.ipv4.IPv4Constants;
import org.jnode.net.ipv4.IPv4Header;
import org.jnode.net.ipv4.icmp.ICMPEchoHeader;
import org.jnode.net.ipv4.icmp.ICMPListener;
import org.jnode.net.ipv4.icmp.ICMPProtocol;
import org.jnode.net.ipv4.layer.IPv4NetworkLayer;
import org.jnode.net.util.NetUtils;
import org.jnode.shell.AbstractCommand;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.HostNameArgument;
/**
* @author JPG
*/
public class PingCommand extends AbstractCommand implements ICMPListener {
private static final String help_host = "the target host";
private static final String help_super = "Ping the specified host";
private static final String fmt_unknown_host = "Unknown host: %s";
private static final String fmt_ping = "Ping %s attempt %d%n";
private static final String fmt_reply = "Reply from %s: %d bytes of data ttl=%d seq=%d time=%dms%n";
private static final String fmt_stats = "-> Packet statistics%n%s%n";
private static final String fmt_get_stats = "%d packets transmitted, %d packets received%nround-trip min/avg/max" +
" = %d/%.3f/%dms";
// FIXME Some of the following could be command parameters ...
private final Statistics stat = new Statistics();
private boolean wait = true;
private int count = 4;
private boolean dontFragment = false;
private IPv4Address dst;
private boolean flood = false;
private int interval = 6000;
private int size = 64;
private long timeout = 5000;
private int ttl = 255;
private final HostNameArgument argHost;
public PingCommand() {
super(help_super);
argHost = new HostNameArgument("host", Argument.MANDATORY, help_host);
registerArguments(argHost);
}
public static void main(String[] args) throws Exception {
new PingCommand().execute(args);
}
public void execute() throws SocketException, InterruptedException {
try {
this.dst = new IPv4Address(argHost.getAsInetAddress());
} catch (UnknownHostException ex) {
getError().getPrintWriter().format(fmt_unknown_host, ex.getLocalizedMessage());
exit(1);
}
final PrintWriter out = getOutput().getPrintWriter();
final IPv4Header netHeader =
new IPv4Header(0, this.ttl, IPv4Constants.IPPROTO_ICMP, this.dst, 8);
netHeader.setDontFragment(this.dontFragment);
final IPv4NetworkLayer netLayer =
(IPv4NetworkLayer) NetUtils.getNLM().getNetworkLayer(EthernetConstants.ETH_P_IP);
final ICMPProtocol icmpProtocol =
(ICMPProtocol) netLayer.getProtocol(ICMPProtocol.IPPROTO_ICMP);
icmpProtocol.addListener(this);
try {
int id_count = 0;
int seq_count = 0;
while (this.count != 0) {
out.format(fmt_ping, dst, seq_count);
if (!this.flood) {
this.wait = true;
}
SocketBuffer packet = new SocketBuffer();
packet.insert(this.size);
ICMPEchoHeader transportHeader = new ICMPEchoHeader(id_count, seq_count);
transportHeader.prefixTo(packet);
Request r =
new Request(this.stat, this.timeout, System.currentTimeMillis(), id_count,
seq_count);
registerRequest(r);
netLayer.transmit(netHeader, packet);
while (this.wait) {
long time = System.currentTimeMillis() - r.getTimestamp();
if (time > this.interval) {
this.wait = false;
}
Thread.sleep(500);
synchronized (this) {
if (response) {
out.format(fmt_reply,
dst.toString(), hdr1.getDataLength(), hdr1.getTtl(), hdr2.getSeqNumber(), roundt);
response = false;
}
}
}
this.count--;
seq_count++;
}
while (!isEmpty()) {
Thread.sleep(100);
}
} finally {
icmpProtocol.removeListener(this);
}
out.format(fmt_stats, this.stat.getStatistics());
}
private long match(int id, int seq, Request r) {
if (r != null && id == r.getId()) {
return r.getTimestamp();
} else {
return -1;
}
}
public void packetReceived(SocketBuffer skbuf) {
long received = System.currentTimeMillis();
IPv4Header hdr1 = (IPv4Header) skbuf.getNetworkLayerHeader();
ICMPEchoHeader hdr2 = (ICMPEchoHeader) skbuf.getTransportLayerHeader();
int seq = hdr2.getSeqNumber();
Request r = removeRequest(seq);
if (r == null || r.Obsolete()) {
return;
}
long timestamp = match(hdr2.getIdentifier(), seq, r);
long roundtrip = received - timestamp;
gotResponse(timestamp, hdr1, hdr2, roundtrip);
}
private synchronized void gotResponse(long timestamp, IPv4Header hdr1, ICMPEchoHeader hdr2,
long roundtrip) {
if (timestamp != -1) {
this.hdr1 = hdr1;
this.hdr2 = hdr2;
this.roundt = roundtrip;
response = true;
}
wait = false;
this.stat.recordPacket(roundtrip);
}
// response data
private boolean response;
private long roundt;
private IPv4Header hdr1;
private ICMPEchoHeader hdr2;
// requests are tracked here
private Map<Integer, Request> requests = new HashMap<Integer, Request>();
private void registerRequest(Request r) {
requests.put(r.seq, r);
}
private Request removeRequest(int seq) {
return requests.remove(seq);
}
private boolean isEmpty() {
return requests.isEmpty();
}
class Request extends TimerTask {
private Timer timer = new Timer();
private boolean obsolete = false;
private Statistics stat;
private long timestamp;
private int id, seq;
Request(Statistics stat, long timeout, long timestamp, int id, int seq) {
this.stat = stat;
this.timestamp = timestamp;
this.id = id;
this.seq = seq;
timer.schedule(this, timeout);
}
public void run() {
if (!this.Obsolete()) {
stat.recordLost();
removeRequest(this.seq);
}
}
synchronized boolean Obsolete() {
if (!obsolete) {
this.obsolete = true;
this.timer.cancel();
return false;
} else {
return true;
}
}
long getTimestamp() {
return timestamp;
}
int getId() {
return id;
}
int getSeq() {
return seq;
}
}
private class Statistics {
private int received = 0, lost = 0;
private long min = Integer.MAX_VALUE, max = 0;
private long sum;
void recordPacket(long roundtrip) {
received++;
if (roundtrip < min) {
min = roundtrip;
}
if (roundtrip > max) {
max = roundtrip;
}
sum += roundtrip;
}
void recordLost() {
lost++;
}
String getStatistics() {
int packets = received + lost;
float avg = sum / packets;
return String.format(fmt_get_stats, packets, received, min, avg, max);
}
}
}