使用pcap4j進行抓包


Troubleshooting是我平時工作中的重要內容,我幾乎每天都會花一些時間在定位客戶環境的問題上,有很多的問題都需要通過抓包來協助分析,比如定位SSL handshake失敗,SNMP請求沒響應的問題等。Linux平台一般使用tcpdump抓包,由於我們只能通過遠程腳本調用的方式執行,所以對windows我沒法使用wireshark之類的GUI工具,所以一般用netsh( 參考資料3)進行抓包。但是,linux上有時候並沒有安裝tcpdump或者登錄的用戶沒有權限執行tcpdump,而且windows上使用netsh抓包很麻煩,而且過濾方式很弱,所以,我們就想着能不能有其他的抓包方式。

Pcap4j剛好滿足我們的要求,下面接單介紹下使用pcap4j如何實現抓包。

創建項目

創建一個maven項目,設置完項目后,將pcap4j的依賴加到pom.xml中:

<!--pom.xml-->
<!--pcap4j imports-->
<dependency>
    <groupId>org.pcap4j</groupId>
    <artifactId>pcap4j-core</artifactId>
    <version>1.7.3</version>
</dependency>
<dependency>
    <groupId>org.pcap4j</groupId>
    <artifactId>pcap4j-packetfactory-static</artifactId>
    <version>1.7.3</version>
</dependency>

打開網卡抓第一個包

Pcap4j提供了一個很好的工具類org.pcap4j.core.Pcaps,可以方便的根據名字獲得一個網卡。拿到網卡后,打開一個PcapHandle,並獲取第一個包並打印包信息。

// 根據網卡名獲取網卡
PcapNetworkInterface nif = Pcaps.getDevByName(name);
int snapLen = 65536;
PromiscuousMode mode = PromiscuousMode.PROMISCUOUS;
int timeout = 10;
// 打開一個句柄
PcapHandle handle = nif.openLive(snapLen, mode, timeout);
// 獲取一個包
Packet packet = handle.getNextPacketEx();
handle.close();
System.out.println(packet);

可以通過ifconfig先查下網卡名字。Pcap也可以列出所有的網卡,然后自己過濾:

List<PcapNetworkInterface> inters = Pcaps.findAllDevs();
// select you interface

抓指定數量的包

通常,我們抓包的時候並不是抓一個包就夠了。Pcap提供了一個抽象的方法,可以連續抓多個包。

// Create a listener to handle the packets
PcapNetworkInterface nif = Pcaps.getDevByName(name);
int snapLen = 65536;
PromiscuousMode mode = PromiscuousMode.PROMISCUOUS;
int timeout = 10;
// 打開一個句柄
PcapHandle handle = nif.openLive(snapLen, mode, timeout);

// 自定義一個Packet的listener處理抓到的包
PacketListener listener = new PacketListener() {
    @Override
    public void gotPacket(Packet packet) {
        System.out.println(handle.getTimestamp());
        System.out.println(packet);
    }
};

// 讓handle使用創建的listener,且指定抓50個包
try {
    int maxPackets = 50;
    handle.loop(maxPackets, listener);
} catch (InterruptedException e) {
    e.printStackTrace();
}

handle.close();

設置filter

就像使用tcpdump一樣,我們不希望把所有的包都抓到,所以我們會在運行tcpdump的時候指定一個filter。同樣,Pcap4j也支持一樣的filter語法:

// 打開網卡
.....
// 打開handle
final PcapHandle handle = device.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);

// 設置filter過濾經過443端口的TCP包
String filter = "tcp port 443";
handle.setFilter(filter, BpfCompileMode.OPTIMIZE);

// 其他代碼。。。

寫到PCAP文件

通常抓包是在產品環境或者客戶環境上,我們沒法直接對抓到的包處理,常用的方法是保存到文件,然后下載下來用wireshark打開再分析。Pcap4j可以很方便的把抓到的包保存到pcap文件中。

....
// 獲取網卡並打開handle
....
// 通過handle創建一個dumper
PcapDumper dumper = handle.dumpOpen("capturedPackets.pcap");
// ....抓包....
// 處理包的時候保存到文件
try {
    dumper.dump(packet, handle.getTimestamp());
} catch (NotOpenException e) {
    e.printStackTrace();
}

// 關閉句柄
dumper.close();


下面是完整的實例代碼:

package cc.databus.netool;

import com.sun.jna.Platform;
import org.pcap4j.core.*;
import org.pcap4j.packet.Packet;

import java.io.EOFException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

public class DumpPacketsDemo {
    public static void main(String[] args) throws PcapNativeException, EOFException, TimeoutException, NotOpenException {
        if (args.length < 2) {
            System.err.println("netool <ethName> <count>");
            return;
        }

        String nifName = args[0];
        int count = Integer.parseInt(args[1]);

        // 1. get network interface
        PcapNetworkInterface nif = Pcaps.getDevByName(nifName);
        if (nif == null)  {
            System.err.println("Cannot get interfance - " + nifName);
            return;
        }
        // 2. open handle
        PcapNetworkInterface.PromiscuousMode mode = PcapNetworkInterface.PromiscuousMode.PROMISCUOUS;
        int timeout = 10;
        int snapLen = 65536;
        PcapHandle handle = nif.openLive(snapLen, mode, timeout);

        // 4. set pcap dumper
        final PcapDumper dumper = handle.dumpOpen("dump.pcap");

        final AtomicLong dumped = new AtomicLong(0);
        try {
            // 5. set filter
            handle.setFilter("tcp port 443", BpfProgram.BpfCompileMode.OPTIMIZE);

            // 6. prepare listener
            PacketListener listener = new PacketListener() {
                @Override
                public void gotPacket(Packet packet) {
                    try {
                        dumper.dump(packet);
                        dumped.incrementAndGet();
                    }
                    catch (NotOpenException ignore) {
                    }
                }
            };

            // 7. start looper
            try {
                handle.loop(count, listener);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }

            // Print out handle statistics
            PcapStat stats = handle.getStats();
            System.out.println("Pakcets dumped: " + dumped.get());
            System.out.println("Packets received: " + stats.getNumPacketsReceived());
            System.out.println("Packets dropped: " + stats.getNumPacketsDropped());
            System.out.println("Packets dropped by interface: " + stats.getNumPacketsDroppedByIf());
            // Supported by WinPcap only
            if (Platform.isWindows()) {
                System.out.println("Packets captured: " +stats.getNumPacketsCaptured());
            }
        }
        finally {
            dumper.close();
            handle.close();
        }
    }
}

參考文獻

  1. Pcap4j官網
  2. 基於Pcap4j實現的抓包工具
  3. Windows使用netsh抓包

文章同步發布在我的個人博客https://jianyuan.me上,歡迎拍磚。
傳送門: 使用Pcap4j實現一個抓包工具


免責聲明!

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



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