前段時間搞抓包程序,打算使用Pcap4J實現,發現除了GitHub,其它資料少之又少,幾乎都是不起作用。
被迫我一直看(日本作者!)英文注解的源碼和sample和test,比較費勁+營養很少。因為幾乎都是解析本地保存的抓包文件 同 初始化好的包類型對象做比較,沒有對真實的網絡環境抓包舉例。都按指定類型初始化對象不是重點好不好,重點是怎么把抓到的包轉換到指定類型!
說一下我們要使用Pcap4J的原因。
之前一直是使用jNetPcap的,但是它已經很久不更新代碼了,並且在linux上跑不起來。使用時還需要根據操作系統加載不同的jar包。
Pcap4J和jNetPcap相同之處,都是基於libpcap/winpcap的。於是乎,過濾器規則也是通用的(比較關鍵)。過濾器寫法可以直接去wareshark驗證。
代碼使用上,Pcap4J 和 jNetPcap很相似,不同的是,Pcap4J 比jNetPcap 封裝的更多些,其實不好,它想讓我們寫的更少,卻不太靈活了。
Pcap4J 不用分操作系統引用不同的包,支持linux比較好,比較不錯。
更多優點去參考Github,據說Pcap4J既可以抓包,又可以發包,還多支持了SNMP。當然,對linux的良好支持就夠打動我了。
maven 引用的部分
<dependencies>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packetfactory-static</artifactId>
<version>1.6.3</version>
</dependency> ... </dependencies>
開始貼代碼吧,只是大體輪廓,意思都到了。
// 獲取所有網卡設備
List<PcapNetworkInterface> alldev = Pcaps.findAllDevs(); // 根據設備名稱初始化抓包接口
PcapNetworkInterface nif = Pcaps.getDevByName(alldev.get(devicenum).getName()); // 抓取包長度
int snaplen = 64 * 1024; // 超時50ms
int timeout = 50; // 初始化抓包器
PcapHandle.Builder phb = new PcapHandle.Builder(nif.getName()).snaplen(snaplen) .promiscuousMode(PromiscuousMode.PROMISCUOUS).timeoutMillis(timeout) .bufferSize(1 * 1024 * 1024); PcapHandle handle = phb.build(); // handle = nif.openLive(snaplen, PromiscuousMode.NONPROMISCUOUS, timeout);
/** 設置TCP過濾規則 */ String filter = "ip and tcp and (dst host 127.0.0.1 and dst port 80)"; // 設置過濾器
handle.setFilter(filter, BpfCompileMode.OPTIMIZE);
filter過濾器只是例子,要根據自身需求寫過濾器。
再寫個loop,也就是觀察者模式(高大上了有木有!),抓到包后就回調。
//初始化listener
PacketListener listener = new PacketListener() { @Override public void gotPacket(Packet packet) { System.out.println(packet); } }; //直接使用loop
handle.loop(COUNT, listener);
這里沒寫try-catch 也沒啥牛逼的邏輯。代碼意思就是抓到包后,都調gotPacket方法。
這里也可以實現自己的loop即無限循環處理包,方法是
Packet packet = handle.getNextPacket();
就是返回抓到的包。這其實是個阻塞方法,就是說可以一個接一個抓到包然后處理,而不是說抓到一個包后它不等你,你還處理呢,后面的包都溜了。
再往下走,其實比較惡心了。困擾了好幾天。
鋪墊下,以下處理的都是TCP/IPv4協議包。
說下惡心的原因。
handle 獲取到的包,都是原始類型的packet,debug看到的叫unknownPacket類型。而從各種例子,網絡,包括作者的test + sample舉例,都沒發現怎么把未知類型的packet 轉換到可以使用的包類型,如TcpPacket,IpV4Packet(對了,這里作者還分了v4 和 v6 兩個版本的IP協議),它們都是Packet的子類。
然后我們逼不得已,對着TCP/IP協議的教材(是一種對網絡知識的復習!),搞一套實現方式。
貼代碼了,但願我寫的代碼注釋能釋然你們。
byte[] rawData = pcapPacket.getRawData(); // 如果抓包內容長度都小於最小硬件協議長度,則直接返回。 if (rawData.length < 14) { return; } IpV4Packet ipV4Packet = null; TcpPacket tcpPacket = null; // 由於默認過濾器過濾為IP和TCP協議包,可以直接判斷rawData長度。 // 只判斷IpV4協議,通過rawData數據得出IpV4頭部長度。header_length標識在rawDta第15字節值,即(刨去前14位Ethernet協議長度)的后4個bit, // 則IpV4協議頭部長度,最長為4位二進制數最大值15(4bit最大值) * 4 = 60 字節(1字節為8位即rawData數組中的一個數字) int ipV4HeaderLength = Integer.parseInt(Integer.toHexString(rawData[14]).charAt(1) + "") * 4; ipV4Packet = IpV4Packet.newPacket(rawData, 14, ipV4HeaderLength); // tcpOffset 是tcp協議開始的部分,開始於Ethernet協議和IpV4協議頭部之后,存在於IpV4協議數據部分里。 int tcpOffset = 14 + ipV4HeaderLength; // 方法解釋:rawData數據, 頭部的長度(按非數據內容處理),數據的長度(整個長度-非數據內容長度) tcpPacket = TcpPacket.newPacket(rawData, tcpOffset, rawData.length - tcpOffset);
我解釋一下,其實上面的代碼真沒經過太牛逼的檢驗,但是簡單測試N次是符合我們要求的。
我們是通過分析包內容rawData來轉換對象的,用的都是包對象的構造方法。
首先,截取到的rawData是分層次的(我們只要TCP和IP協議的東西),從頭開始依次是:Ethernet協議,IpV4協議,TCP協議。
再者,每個協議的定義是有規律的,都包含在rawData內容里。這樣我們就可以分析。
水平有限,只能簡單解釋一下每個協議的分析。
1位byte = 8bit 即 兩個 16進制數。
Ethernet協議: 長度是14位是比較固定的, 包含:6位目的mac地址 + 6位源mac地址 + 2位協議類型。
IPv4協議: 頭部長度在整個長度第 15位 的第2個 16進制數上。 具體是換算成10進制后*4 的長度,即最長是60位。為什么乘以4,我解釋不了,可能4是4bit的意思,默認的規則。頭部長度最小是20位,深究的自己查一下吧。
TCP協議: IPv4協議的數據部分,就都是TCP協議的內容了,可以直接來用了。
就寫到這里了,抓的包可以隨便來用了,IP協議里有IP,TCP協議里有端口有內容有序列號,都有了。
這樣來講,要抓其他協議的包也可以這么生成,但是,如果有其他更好的實現方式一定要告訴我!
----------------
更新一下,現在Pcap4J應該可以直接轉換到各種協議的包了,不用像我這么包裝。