Saturday 1 June 2013

Capture packets in Java using JPCAP (Ping packets)

      Java does not provide any inbuilt libraries for capturing IP packets. Hence we must rely on third party libraries for this task. JPCAP is one such library. JPCAP is an open source library for capturing and sending network packets from Java applications. It provides facilities to :

1. capture raw packets live from the wire.
2. save captured packets to an offline file, and read captured packets from an offline file.
3. automatically identify packet types and generate corresponding Java objects (for Ethernet, IPv4, IPv6, ARP/RARP, TCP, UDP, and ICMPv4 packets).
4. filter the packets according to user-specified rules before dispatching them to the application.
5. send raw packets to the network

      JPCAP makes use of native libraries for capturing packets. For Windows it uses WinPcap and for UNIX it relies on libpcap. As an example we shall capture the ping messages received by our machine. A small description of Ping is given below. 

Ping


      Ping is used to test the reachability of a host on an Internet Protocol (IP) network. It operates by sending Internet Control Message Protocol (ICMP) echo request packets to the target host and waiting for an ICMP echo reply messages. In the process it measures the time from transmission to reception (round-trip time) and records any packet loss.


Downloading dependencies & Setting up the environment



Downloads :


      Since JPCAP relies on WinPcap(for Windows)/libpcap(for UNIX), we must first download and install them. For Windows, WinPcap can be downloaded from the site http://www.winpcap.org. This must now be installed on your system. Also we need the JPCAP library, which can be downloaded from the link http://sourceforge.net/projects/jpcap/. If the links dont work simply search for them on google and choose the first links suggested.

Setup :


      For JPCAP to work you need to do the following two things :

1. Add jpcap.dll to the Java bin folder. The following steps accomplish this :
a.) Extract the downloaded JPCAP folder.
b.) Navigate to the lib folder inside the extracted folder. (i.e. navigate to jpcap-0.01.16-win32\lib. Note that the name of the downloaded folder may be different for a different version. It will however be of the form jpcap-...-win32). The lib folder must contain the jpcap.dll file.
c.) Copy the jpcap.dll file.
d.) Paste it in your jdk's bin folder. (Navigate to your Java folder. Open the jdk folder. Open the bin folder. Paste the file here)

2. Add the jpcap jar file to your classpath. The jpcap jar file is the file net.sourceforge.jpcap-0.01.16 included in the jars folder of the downloaded JPCAP folder. (If you are using Eclipse, you can do this by right clicking on the project, selecting Build Path, selecting add external archives and adding the required jar file).

The CODE!



PART 1 : To capture any packets we must use one of the network interfaces of our machine. To get the id of the network interface we use the following short java program.(Alternatively you can type ipconfig in the command prompt to get the device ID)

import net.sourceforge.jpcap.capture.CaptureDeviceNotFoundException;
import net.sourceforge.jpcap.capture.PacketCapture;
public class GetDeviceID {
    public static void main(String args[]){
        try {
           PacketCapture capture = new PacketCapture();
           String device=capture.findDevice();
           System.out.println(device);
        } catch (CaptureDeviceNotFoundException e) {
           // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

The above programs displays something like :

\Device\NPF_{04FGHGHA-D453-4450-945E-1145454557B4}
  Microsoft

Note down the part from the start of the string to the closing brace. This is the interface that we will listen to for packets.

PART 2 : Now we can capture the packets using the code given below.
The device ID being used is the one we found above. So replace the one shown below with your own. Note how the Device ID is specified in the call to capture.open().


import net.sourceforge.jpcap.capture.CaptureDeviceLookupException;
import net.sourceforge.jpcap.capture.PacketCapture;
import net.sourceforge.jpcap.capture.PacketListener;
import net.sourceforge.jpcap.net.ICMPMessages;
import net.sourceforge.jpcap.net.ICMPPacket;
import net.sourceforge.jpcap.net.Packet;

public class PingCapture {
    public static void main(String[] args) throws CaptureDeviceLookupException {
        Capture cap = new Capture();
        cap.doCapture();
    }
}

class PingListener implements PacketListener {
    @Override
    public void packetArrived(Packet packet) {
        try {
            // only ICMP packages
            if (packet instanceof ICMPPacket) {
                ICMPPacket tcpPacket = (ICMPPacket) packet;
                int data = tcpPacket.getMessageCode();
                // only echo request packages
                if (data == ICMPMessages.ECHO) {
                    // print source and destination.
                    String srcHost = tcpPacket.getSourceAddress();
                    String dstHost = tcpPacket.getDestinationAddress();
                    System.out.println("Ping from: " + srcHost + " to " + dstHost);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Capture {
    public void doCapture() {
        // create capture instance
        PacketCapture capture = new PacketCapture();
        // add listener that handles incoming and outgoing packages
        PingListener pingListener = new PingListener();
        capture.addPacketListener(pingListener);
        try {
            capture.open("\\Device\\NPF_{04FGHGHA-D453-4450-945E-1145454557B4}", true); // Replace the device ID with your own
            while (true) {
                capture.capture(1); // capture one packet, since this is in a loop, it will keep on capturing more packets
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            capture.removePacketListener(pingListener);
            capture.endCapture();
            capture.close();
        }
    }
}


      If everything went correctly, you will be able to see any ping requests received by you machine. If no ping messages are coming then nothing will be displayed. In this case to make sure whether the code is working, add a System.out.println() as the first statement of the packetArrived method. This will verify whether any packets are being received.

      Some common errors that may come up when you try to run the above code  are listed below:

Error 1: 
PacketCapture: loading native library jpcap.. Exception in thread "main" java.lang.UnsatisfiedLinkError: no jpcap in java.library.path
at java.lang.ClassLoader.loadLibrary(Unknown Source)
Solution: jpcap.dll file has not been copied to the java bin folder. See point #1 in the setup above to rectify this.


Error 2:
net.sourceforge.jpcap.capture.CaptureDeviceOpenException: Error opening adapter: The system cannot find the device specified. (20)
at net.sourceforge.jpcap.capture.PacketCapture.open(Native Method)
Solution: The device name is not correct. Replace the device specified in the above code to you own device ID. Note how the ID is specified(with two backslashes).


Error 3:
Getting errors during compilation of the form
error: package net.sourceforge.jpcap.capture does not exist
import net.sourceforge.jpcap.capture.CaptureDeviceLookupException;
Solution: Add the jpcap jar file to classpath. See point #2 in the setup above to rectify this.