说明:
之前的项目需要一个Windows和Linux平台都能用的抓包模块,搜了之后发现jnetpcap、jpcap都已经很久没有更代码了,而且跨平台支持不好,所以考虑用Pcap4J。
Pcap4J和jNetPcap相同之处,都是基于libpcap/winpcap的。所以于是乎,过滤器规则也是通用的(pcap规则),过滤器写法可以直接去wireshark验证。
代码使用上,Pcap4J 和 jNetPcap很相似,不同的是,Pcap4J 比jNetPcap 封装的更多些,作者说的是Pcap4J既可以抓包,又可以发包,但是我只用了抓包,更多功能还可以研究一下。
Pcap4J有关的资料,看了看其他的博客都不是很靠谱,也就只有Github上的资料了,所以用是能用上了,但很多问题我也没有找到解决方案。
Pcap4J官网:https://www.pcap4j.org/
Github地址:https://github.com/kaitoy/pcap4j
使用:
第一步:
安装本地抓包库,Linux下安装libpcap;Windows下安装Winpcap或者Npcap
注:2013年3月8日发布以来,WinPcap的开发已经停止,而Npcap仍在开发中。 因此,如果要使用新功能,则应选择Npcap。Linux下运行需要root权限。
第二步:
添加依赖
2019年1月2号更新的版本为1.7.5,maven依赖我们也填这个版本号
<dependencies>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packetfactory-static</artifactId>
<version>1.7.5</version>
</dependency>
...
</dependencies>
抓包:
接下来就可以使用了,我们看一下simple里面Loop.java的代码,去掉了一些无用的sysout:
public class Loop {
private static final String COUNT_KEY = Loop.class.getName() + ".count";
private static final int COUNT = Integer.getInteger(COUNT_KEY, 5);
private static final String READ_TIMEOUT_KEY = Loop.class.getName() + ".readTimeout";
private static final int READ_TIMEOUT = Integer.getInteger(READ_TIMEOUT_KEY, 10); // [ms]
private static final String SNAPLEN_KEY = Loop.class.getName() + ".snaplen";
private static final int SNAPLEN = Integer.getInteger(SNAPLEN_KEY, 65536); // [bytes]
private Loop() {}
public static void main(String[] args) throws PcapNativeException, NotOpenException {
String filter = “ip and tcp and (dst host 127.0.0.1 and dst port 80)”; // 设置过滤的字符串
// 设置要抓包的网卡
PcapNetworkInterface nif;
try {
nif = new NifSelector().selectNetworkInterface(); // 这个方法提供用户输入网卡的序号
} catch (IOException e) {
e.printStackTrace();
return;
}
if (nif == null) {
return;
}
// 实例化一个捕获报文的对象,设置抓包参数:长度,混杂模式,超时时间等
final PcapHandle handle = nif.openLive(SNAPLEN, PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
// 设置过滤器
if (filter.length() != 0) {
handle.setFilter(filter, BpfCompileMode.OPTIMIZE);
}
// 观察者模式,抓到报文回调gotPacket方法处理报文内容
PacketListener listener =
new PacketListener() {
@Override
public void gotPacket(Packet packet) {
// 抓到报文走这里...
System.out.println(handle.getTimestamp());
System.out.println(packet);
}
};
// 直接使用loop无限循环处理包
try {
handle.loop(COUNT, listener); // COUNT设置为抓包个数,当为-1时无限抓包
} catch (InterruptedException e) {
e.printStackTrace();
}
handle.close();
}
}
例子的一些扩展:
过滤器这个字符串可以自己设置,如果拿不准,可以在wireshark上面抓包过滤器上设置一下验证。
设置网卡我们也可以得到所有网卡设备,然后根据IP地址或者网卡名来设置,这样:
// 获取所有网卡设备
List<PcapNetworkInterface> alldev = Pcaps.findAllDevs();
// 根据设备名称初始化抓包接口
PcapNetworkInterface nif = Pcaps.getDevByName(alldev.get(devicenum).getName());
每一个PcapHandle对象对应抓一个网卡的报文,所以要捕获多网卡就要设置多个PcapHandle,我们可以把这个封装成一个方法,传入网卡对象,返回PcapHandle。
public PcapHandle getPcapHandlerB(PcapNetworkInterface nif) throws PcapNativeException {
PcapHandle.Builder phb = new PcapHandle.Builder(nif.getName()).snaplen(SNAPLEN).promiscuousMode(PromiscuousMode.PROMISCUOUS).timeoutMillis(READ_TIMEOUT);
return phb.build();
}
基本的抓包例子就是这样,事实上我们抓到报文一般都会进行一些处理,一些转义。
报文监听的部分我们可以单独用一个类来现实PacketListener,上面的例子只是用于说明。
我们可以设置好PcapHandle对象后,同时实现一个自定义的PacketListener,转异步再调PcapHandle的Loop方法进行抓包和处理,这样就不会阻塞主线程的执行。
除了gotPacket可以捕获,还有一个带返回值的方法:
Packet packet = handle.getNextPacket(); 这个返回抓到的包,是个阻塞方法,就是说可以一个接一个抓到包然后处理。
抓包后处理:
上面的例子讲怎么样用Pcap4J来抓网卡的报文,还是比较简单的,接下来我们说一下,抓到报文之后的处理。
Pcap4J内置支持的协议报文类型
用于描述,我们实现一个PacketListener专门用来处理进来的packet:
public class MyPacketListener implements PacketListener {
@Override
public void gotPacket(PcapPacket packet) {
// 开始处理报文
/* 您捕获的数据包包括某些协议的标头和有效负载,如以太网,IPv4和TCP。
* Pcap4J的Packet API使您可以从协议标头中获取信息。
*/
IpV4Packet ipV4Packet = packet.get(IpV4Packet.class); // 直接获取IpV4报文
Inet4Address srcAddr = ipV4Packet.getHeader().getSrcAddr();
System.out.println(srcAddr); // 输出源IP地址
// 可以直接get你想要的报文类型,只要Pcap4J库原生支持
EthernetPacket ethernetPacket = packet.get(EthernetPacket.class); // 以太网报文
TcpPacket tcpPacket = packet.get(TcpPacket.class); // TCP报文
// 也可以通过getPayload()的方式一层一层读取
EthernetHeader ethernetHeader = ethernetPacket.getHeader(); // 读取以太网帧头部
IpV4Packet ipV4Packet2 = (IpV4Packet)ethernetPacket4j.getPayload(); // 注意get出来的类型,强转可能抛异常
// 若需要解析的协议Pcap没有支持,那就需要自己实现这个报文的Java类,然后写反序列化方法了
byte[] rawData = ethernetPacket.getRawData(); // 获取以太网的原始二进制数据
// 然后调你自己对应的反序列化方法解析这个二进制
// TO-DO ...
}
}
若需要解析的协议Pcap没有支持,可以自己添加,具体可参考,这里不展开了:
https://github.com/kaitoy/pcap4j/blob/v1/www/HowToAddProtocolSupport.md
抓到包后,处理了之后,后面的就是自己的代码逻辑了。
TIPS:
自己使用肯定会遇到各种各样的问题,如果遇到了问题,可以在作者Issues下搜一下有没有类似的:
https://github.com/kaitoy/pcap4j/issues?page=1&q=is%3Aissue+is%3Aclosed
如果没有,那就直接提问吧,作者还是回复比较快的,至少我问的几个问题都回复了。。。。。