使用Pcap4j实现一个抓包工具

Troubleshooting是我平时工作中的重要内容,我几乎每天都会花一些时间在定位客户环境的问题上,有很多的问题都需要通过抓包来协助分析,比如定位SSL handshake失败,SNMP请求没响应的问题等。Linux平台一般使用tcpdump抓包,由于我们只能通过远程脚本调用的方式执行,所以对windows我没法使用wireshark之类的GUI工具,所以一般用netsh( 参考资料3)进行抓包。但是,linux上有时候并没有安装tcpdump或者登录的用户没有权限执行tcpdump,而且windows上使用netsh抓包很麻烦,而且过滤方式很弱,所以,我们就想着能不能有其他的抓包方式。

Pcap4j刚好满足我们的要求,下面简单介绍下使用pcap4j如何实现抓包。

创建项目

创建一个maven项目,设置完项目后,将pcap4j的依赖加到pom.xml中:

1
2
3
4
5
6
7
8
9
10
11
12
<!--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,并获取第一个包并打印包信息。

1
2
3
4
5
6
7
8
9
10
11
// 根据网卡名获取网卡
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也可以列出所有的网卡,然后自己过滤:

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

抓指定数量的包

通常,我们抓包的时候并不是抓一个包就够了。Pcap提供了一个抽象的方法,可以连续抓多个包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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语法:

1
2
3
4
5
6
7
8
9
10
// 打开网卡
.....
// 打开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文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
....
// 获取网卡并打开handle
....
// 通过handle创建一个dumper
PcapDumper dumper = handle.dumpOpen("capturedPackets.pcap");
// ....抓包....
// 处理包的时候保存到文件
try {
dumper.dump(packet, handle.getTimestamp());
} catch (NotOpenException e) {
e.printStackTrace();
}

// 关闭句柄
dumper.close();


下面是完整的实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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抓包
初窥AspectJ BTrace的一些限制

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×