http://jnetpcap.com/examples
在上一期的栏目中我们介绍了通过Fiddler嗅探Http协议网络数据包的方法,并且在文章最后通过开心农场的例子来展示网络嗅探的基本操作。但手工获得数据毕竟耗时耗力,颇为麻烦,不妨将这个工作交给电脑,写一个程序让电脑在点击好友的时候自动嗅探到信息数据包并进行处理。这期我们就来介绍一下如何在Java平台下通过第三方包来进行底层网络嗅探。
Java平台本身不支持底层网络操作,需要第三方包利用JNI封装不同系统的C库来提供Java的上层接口。常用的类库包括JPcap,JNetPcap等,他们都是基于TcpDump/LibPcap的Java封装。其中JPcap已经一年多没更新了,而JNetPcap (jnetpcap.com) 在上周刚刚发布了1.2 RC5版本,添加了很多实用的类库,诸如高级协议分析等,本文就以JNetPcap作为例子来进行介绍。使用JNetPcap之前必须在目标系统中安装WinPcap(Windows系统),以提供JNetPcap所需要的链接库。另外要把JNetPcap包和所属的dll文件都加入到开发目录的环境变量中。
Pcap类是JNetPcap中最为核心的类,是一个对LibPcap中方法的Java直接映射,提供了取得网卡设备列表、打开嗅探、设置过滤器等等必须的工作。
一、获得网卡列表
通过Pcap.findAllDevs(alldevs, errbuf) 这个静态方法将所有本机网卡加入到alldevs的List<PcapIf >中。然后用户可以选择一个网卡进行监听。注意基于PPPOE拨号的网络连接在实际测试中似乎并不能被嗅探到,例如笔者的铁通连接无法被嗅探,其中的问题还有待考证。
二、打开连接
调用Pcap.openLive(device.getName(), snaplen, flags, timeout, errbuf)静态方法,返回一个Pcap对象。其中5个参数分别表示设备的系统名称(不是设备别名)、每次捕捉的数据量、捕捉方式、超时和错误信息缓冲区。推荐的参数配置在JNetPcap的文档中有详细说明,这里不再赘述。需要注意的是超时不宜过小,否则会造成数据包捕捉不完全的问题。时间至少应该保证一个数据包完全接收。
三、开始监听<br>调用pcap.loop(int cnt, JPacketHandler<T> handler, T user) 方法即可进行监听,在loop方法的参数中有两点需要关注,第一点是用户指定的数据包分析器,在之后的文章中将详细介绍;第二点是一个泛型参数,表示传输给分析器的用户指定类型的消息。
四、数据包分析
捕捉到数据包后当然要进行分析。在这里我们使用继承JPacketHandler来实现自己的处理方法。<br>在JPacketHandler有一个nextPacket(JPacket packet, T user) 方法,这是典型的通过事件机制来实现处理数据包的方法。每当Pcap嗅探到一个数据包后,他就会调用用户之前绑定的分析器中的nextPacket方法进行处理。注意这个方法是阻塞的,也就避免了潜在的同步问题。传进的JPacket参数包含了这个数据包中的所有信息,通过不同的内置Header分析器可以分析不同的协议。在最近的RC5版本中甚至加入了HTTP协议和图像数据的直接分析,免去了之前RC4版本中需要通过ACK信息手工拼合chunked的数据包,然后手工分析HTTP协议文本的麻烦。限于篇幅,这里不多做介绍,具体方法可以参照API,我们只简单的调用toString()方法将这个数据包打印在控制台中。
JNetPcap无疑是当前最强大以及最具有潜力的网络数据包捕捉类库。感谢Mark B. 的辛苦工作,让Java的JNI世界更加精彩。
我们经常使用Httpwatch查看HTTP的传输数据。那java能不能做到呢? 下面我们就讲一下如何通过java在windows上获取网卡的数据包,这里我们使用了开源的WinPcap,还有jNetPcap,jNetPcap是对接了WinPcap来截获网卡数据包。
如何截获网卡分3步:
1. 在自己的机器上安装WinPcap。 http://www.winpcap.org/install/default.htm
2. 下载jNetPcap, http://jnetpcap.com/download. 下载下来之后解压,里面2个重要文件jnetpcap.jar,jnetpcap.dll
3 在eclipse里新建工程,把jnetpcap.jar加入library. 然后参考如下代码实现网卡截包:
import java.util.ArrayList;
import java.util.List;
import org.jnetpcap.Pcap;
import org.jnetpcap.PcapIf;
import org.jnetpcap.packet.JPacket;
import org.jnetpcap.packet.JPacketHandler;
import org.jnetpcap.protocol.tcpip.Http;
import org.jnetpcap.protocol.tcpip.Tcp;
/**
* Capture the netword card packages
*
*/
public class App {
public static void main(String[] args) throws InterruptedException {
List<PcapIf> alldevs = new ArrayList<PcapIf>(); // Will be filled with
// NICs
StringBuilder errbuf = new StringBuilder();
int r = Pcap.findAllDevs(alldevs, errbuf);
if (r == Pcap.NOT_OK || alldevs.isEmpty()) {
System.err.printf("Can't read list of devices, error is %s",
errbuf.toString());
return;
}
for (PcapIf pif : alldevs) {
System.out.println(pif.getName());
}
PcapIf pif = alldevs.get(0);//select the device which you want to monitor
/***************************************
* open the device
***************************************/
int snaplen = 64 * 1024; // Capture all packets, no trucation
int flags = Pcap.MODE_PROMISCUOUS; // capture all packets
int timeout = 10 * 1000; // 10 seconds in millis
Pcap pcap = Pcap.openLive(pif.getName(), snaplen, flags, timeout,
errbuf);
if (pcap == null) {
System.err.printf("Error while opening device for capture: "
+ errbuf.toString());
return;
}
/*
* We have an opened the capture file now time to read packets. We use a
* Pcap.loop function to retrieve 10 packets from the file. We supply an
* annonymous handler which will receive packets as they are read from the
* offline file by libpcap. We parameterize it with a StringBuilder class.
* This allows us to pass in any type of object we need inside the our
* dispatch handler. For this example we are passing in the errorbuf object
* so we can pass back a string, if we need to. Of course in our example
* this is not strictly needed since our anonymous class can access errbuf
* object directly from the enclosing main method as that local variable is
* marked final allowing anonymous classes access to it.
*/
pcap.loop(Pcap.LOOP_INFINITE, new JPacketHandler<StringBuilder>() {
/**
* We purposely define and allocate our working tcp header (accessor)
* outside the dispatch function and thus the libpcap loop, as this type
* of object is reusable and it would be a very big waist of time and
* resources to allocate it per every dispatch of a packet. We mark it
* final since we do not plan on allocating any other instances of Tcp.
*/
final Tcp tcp = new Tcp();
/*
* Same thing for our http header
*/
final Http http = new Http();
/**
* Our custom handler that will receive all the packets libpcap will
* dispatch to us. This handler is inside a libpcap loop and will receive
* exactly 10 packets as we specified on the Pcap.loop(10, ...) line
* above.
*
* @param packet
* a packet from our capture file
* @param errbuf
* our custom user parameter which we chose to be a StringBuilder
* object, but could have chosen anything else we wanted passed
* into our handler by libpcap
*/
public void nextPacket(JPacket packet, StringBuilder errbuf) {
/*
* Here we receive 1 packet at a time from the capture file. We are
* going to check if we have a tcp packet and do something with tcp
* header. We are actually going to do this twice to show 2 different
* ways how we can check if a particular header exists in the packet and
* then get that header (peer header definition instance with memory in
* the packet) in 2 separate steps.
*/
if (packet.hasHeader(Tcp.ID)) {
/*
* Now get our tcp header definition (accessor) peered with actual
* memory that holds the tcp header within the packet.
*/
packet.getHeader(tcp);
System.out.printf("tcp.dst_port=%d%n", tcp.destination());
System.out.printf("tcp.src_port=%d%n", tcp.source());
System.out.printf("tcp.ack=%x%n", tcp.ack());
}
/*
* An easier way of checking if header exists and peering with memory
* can be done using a conveniece method JPacket.hasHeader(? extends
* JHeader). This method performs both operations at once returning a
* boolean true or false. True means that header exists in the packet
* and our tcp header difinition object is peered or false if the header
* doesn't exist and no peering was performed.
*/
if (packet.hasHeader(tcp)) {
//System.out.printf("tcp header::%s%n", tcp.toString());
}
/*
* A typical and common approach to getting headers from a packet is to
* chain them as a condition for the if statement. If we need to work
* with both tcp and http headers, for example, we place both of them on
* the command line.
*/
if (packet.hasHeader(tcp) && packet.hasHeader(http)) {
/*
* Now we are guarranteed to have both tcp and http header peered. If
* the packet only contained tcp segment even though tcp may have http
* port number, it still won't show up here since headers appear right
* at the beginning of http session.
*/
System.out.printf("http header::%s%n", http);
/*
* jNetPcap keeps track of frame numbers for us. The number is simply
* incremented with every packet scanned.
*/
}
//System.out.printf("frame #%d%n", packet.getFrameNumber());
}
}, errbuf);
/*
* Last thing to do is close the pcap handle
*/
pcap.close();
}
}
注意:
在运行以上代码之前还需要加上jvm参数,为了让jnetpcap找到jnetpcap.dll,我们在vm parameters加入以下参数:
-Djava.library.path=E:\jnetpcap
这里的E:\jnetpcap 就是jnetpcap.dll放置的目录。