利用Java進行網路資料包捕捉
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放置的目錄。