Pcap4J實現抓包器


  前段時間搞抓包程序,打算使用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應該可以直接轉換到各種協議的包了,不用像我這么包裝。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM