Is Anybody Out There?

Listing 2. With raw sockets you can send ICMP echo requests and implement a Java ping command.

package example;

import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.*;

import org.savarese.vserv.tcpip.*;
import org.savarese.rocksaw.net.RawSocket;

public class Ping {

  public static interface EchoReplyListener {
    public void notifyEchoReply(
      ICMPEchoPacket packet, byte[] data, 
      int dataOffset);
  }

  private RawSocket socket;
  private ICMPEchoPacket packet;
  private int offset, length, dataOffset;
  private byte[] data;
  private int sequence, identifier;
  private EchoReplyListener listener;

  public Ping(int id) throws IOException {
    sequence   = 0;
    identifier = id;
    setEchoReplyListener(null);

    packet = new ICMPEchoPacket(1);
    data   = new byte[84];

    packet.setData(data);
    packet.setIPHeaderLength(5);
    packet.setICMPDataByteLength(56);

    offset = packet.getIPHeaderByteLength();
    dataOffset = 
      packet.getIPHeaderByteLength() +
      packet.getICMPHeaderByteLength();
    length = packet.getICMPPacketByteLength();

    socket = new RawSocket();
    socket.open(RawSocket.PF_INET,
                RawSocket.getProtocolByName("icmp"));
  }

  public void setEchoReplyListener(
    EchoReplyListener l) {
    listener = l;
  }

  public void close() throws IOException {
    socket.close();
  }

  public void sendEchoRequest(InetAddress host)
    throws IOException
  {
    packet.setType(ICMPPacket.TYPE_ECHO_REQUEST);
    packet.setCode(0);
    packet.setIdentifier(identifier);
    packet.setSequenceNumber(sequence++);

    OctetConverter.longToOctets(
      System.nanoTime(), data, dataOffset);
    packet.computeICMPChecksum();

    socket.write(host, data, offset, length);
  }

  public void receive(InetAddress host)
    throws IOException
  {
    socket.read(host, data);
  }

  public void receiveEchoReply(InetAddress host)
    throws IOException
  {
    do {
      receive(host);
    } while(packet.getType() != 
      ICMPPacket.TYPE_ECHO_REPLY ||
      packet.getIdentifier() != identifier);

    if(listener != null)
      listener.notifyEchoReply(
        packet, data, dataOffset);
  }

  public long ping(InetAddress host)
    throws IOException
  {
    sendEchoRequest(host);
    receiveEchoReply(host);

    long end   = System.nanoTime();
    long start =
      OctetConverter.octetsToLong(data, dataOffset);

    return (end - start);
  }

  public int getDataLength() {
    return packet.getICMPDataByteLength();
  }

  public int getPacketLength() {
    return packet.getIPPacketLength();
  }

  public static final void main(String[] args)
    throws Exception
  {

    if(args.length < 1 || args.length > 2) {
      System.err.println("usage: Ping host [count]");
      System.exit(1);
    }

    try{
      final InetAddress address = 
      InetAddress.getByName(args[0]);
      final String hostname =
        address.getCanonicalHostName();
      final String hostaddr =
        address.getHostAddress();
      final int count;

      if(args.length == 2)
        count = Integer.parseInt(args[1]);
      else
        count = 5;

      // Ping programs usually use the process ID for
      // the identifier, but this is only a demo.
      final Ping ping = new Ping(65535);

      ping.setEchoReplyListener(
        new EchoReplyListener() {
          public void notifyEchoReply(
            ICMPEchoPacket packet, byte[] data, 
            int dataOffset) {
            long end   = System.nanoTime();
            long start =
              OctetConverter.octetsToLong(
              data, dataOffset);
            double rtt = (double)(end - start) / 1e6;
            System.out.println(
              packet.getICMPPacketByteLength() + 
              " bytes from " + hostname + " (" + 
              hostaddr + "): icmp_seq=" + 
              packet.getSequenceNumber() +" ttl=" +
              packet.getTTL() +" time=" + rtt + 
              " ms");
          }
        });

      System.out.println("PING " + hostname + " (" +
        hostaddr + ") " + ping.getDataLength() + "(" +
        ping.getPacketLength() + ") bytes of data).");

      final ScheduledThreadPoolExecutor executor =
        new ScheduledThreadPoolExecutor(2);
      final CountDownLatch latch =
        new CountDownLatch(1);

      executor.scheduleAtFixedRate(new Runnable() {
          int counter = count;

          public void run() {
            try {
              if(counter > 0) {
                ping.sendEchoRequest(address);
                if(counter == count)
                  latch.countDown();
                --counter;
              } else
                executor.shutdown();
            } catch(IOException ioe) {
              ioe.printStackTrace();
            }
          }
        }, 0, 1, TimeUnit.SECONDS);

        // We wait for first ping to be sent because
        // Windows times out with WSAETIMIEDOUT if 
        // echo request hasn't been sent first. 
        // POSIX does the right thing and just blocks.
      latch.await();

      for(int i = 0; i < count; ++i)
        ping.receiveEchoReply(address);

      ping.close();
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
}