Spring Boot + Pcap4j 實(shí)現(xiàn)網(wǎng)絡(luò)流量抓包與實(shí)時(shí)分析
前言
在當(dāng)今數(shù)字化時(shí)代,網(wǎng)絡(luò)流量如同信息社會(huì)的血液,承載著海量的數(shù)據(jù)交互。對(duì)網(wǎng)絡(luò)流量進(jìn)行有效的抓包與實(shí)時(shí)分析,是保障網(wǎng)絡(luò)安全、優(yōu)化網(wǎng)絡(luò)性能的關(guān)鍵環(huán)節(jié)。無論是及時(shí)發(fā)現(xiàn)潛在的網(wǎng)絡(luò)攻擊,還是排查網(wǎng)絡(luò)擁塞等問題,都離不開精準(zhǔn)、高效的流量分析手段。
而Pcap4j則是一個(gè)強(qiáng)大的Java庫(kù),它基于libpcap/winpcap,為Java開發(fā)者提供了便捷的網(wǎng)絡(luò)數(shù)據(jù)包捕獲與處理能力。將Spring Boot與Pcap4j相結(jié)合,能夠充分發(fā)揮兩者的優(yōu)勢(shì),快速構(gòu)建出一個(gè)功能完善、易于擴(kuò)展的網(wǎng)絡(luò)流量抓包與實(shí)時(shí)分析系統(tǒng)。
環(huán)境搭建
效果圖
圖片
依賴引入
<dependencies>
<!-- Spring Boot WebSocket依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Pcap4j 依賴 -->
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packetfactory-static</artifactId>
<version>1.8.2</version>
</dependency>
</dependencies>安裝 libpcap/winpcap:Pcap4j需要依賴底層的libpcap(Linux、Mac 統(tǒng))或winpcap(Windows 系統(tǒng))庫(kù)。對(duì)于 Windows 系統(tǒng),可以從官網(wǎng)下載winpcap安裝程序并進(jìn)行安裝;對(duì)于Linux系統(tǒng),通常可以通過包管理器進(jìn)行安裝,如yum install libpcap或apt-get install libpcap-dev。
核心功能實(shí)現(xiàn)
網(wǎng)絡(luò)接口選擇
在進(jìn)行網(wǎng)絡(luò)流量抓包之前,需要先選擇要監(jiān)聽的網(wǎng)絡(luò)接口。Pcap4j提供了獲取網(wǎng)絡(luò)接口列表的方法,我們可以通過編寫代碼讓用戶選擇需要監(jiān)聽的接口。
首先,使用Pcaps.findAllDevs()方法獲取所有可用的網(wǎng)絡(luò)接口列表,然后遍歷該列表,將每個(gè)接口的名稱、描述等信息展示給用戶,讓用戶進(jìn)行選擇。例如:
List<PcapNetworkInterface> allDevs = Pcaps.findAllDevs();
if (allDevs.isEmpty()) {
throw new RuntimeException("No network interfaces found!");
}
// 展示網(wǎng)絡(luò)接口信息
for (int i = 0; i < allDevs.size(); i++) {
PcapNetworkInterface dev = allDevs.get(i);
System.out.println(i + ": " + dev.getName() + " - " + dev.getDescription());
}
// 讓用戶選擇接口
Scanner scanner = new Scanner(System.in);
int index = scanner.nextInt();
PcapNetworkInterface selectedDev = allDevs.get(index);數(shù)據(jù)包捕獲
選擇好網(wǎng)絡(luò)接口后,就可以進(jìn)行數(shù)據(jù)包的捕獲了。使用 Pcap4j 的openLive()方法打開選中的網(wǎng)絡(luò)接口,并設(shè)置捕獲超時(shí)時(shí)間和緩沖區(qū)大小等參數(shù)。然后,通過loop()方法循環(huán)捕獲數(shù)據(jù)包。
在捕獲數(shù)據(jù)包的過程中,可以設(shè)置過濾器,只捕獲符合特定條件的數(shù)據(jù)包,例如只捕獲 TCP 協(xié)議的數(shù)據(jù)包、特定端口的數(shù)據(jù)包等。過濾器的語法遵循 libpcap 的過濾規(guī)則。
// 打開網(wǎng)絡(luò)接口
PcapHandle handle = selectedDev.openLive(65536, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 1000);
// 設(shè)置過濾器,只捕獲TCP協(xié)議且目的端口為80的數(shù)據(jù)包
String filter = "tcp dst port 80";
handle.setFilter(filter, BpfProgram.BpfCompileMode.OPTIMIZE);
// 循環(huán)捕獲數(shù)據(jù)包
handle.loop(-1, new PacketListener() {
@Override
public void gotPacket(Packet packet) {
// 處理捕獲到的數(shù)據(jù)包
processPacket(packet);
}
});數(shù)據(jù)包解析
捕獲到數(shù)據(jù)包后,需要對(duì)其進(jìn)行解析,提取出有用的信息,如源IP地址、目的IP地址、源端口、目的端口、協(xié)議類型、數(shù)據(jù)包長(zhǎng)度等。Pcap4j提供了各種數(shù)據(jù)包類,如IpV4Packet、TcpPacket、UdpPacket等,可以根據(jù)數(shù)據(jù)包的類型進(jìn)行相應(yīng)的解析。
private void processPacket(Packet packet) {
// 解析以太網(wǎng)幀
EthernetPacket ethernetPacket = packet.get(EthernetPacket.class);
if (ethernetPacket == null) {
return;
}
// 解析IP數(shù)據(jù)包
IpV4Packet ipV4Packet = ethernetPacket.get(IpV4Packet.class);
if (ipV4Packet == null) {
return;
}
Inet4Address srcIp = ipV4Packet.getHeader().getSrcAddr();
Inet4Address dstIp = ipV4Packet.getHeader().getDstAddr();
IpNumber protocol = ipV4Packet.getHeader().getProtocol();
// 解析傳輸層協(xié)議
int srcPort = -1;
int dstPort = -1;
if (protocol == IpNumber.TCP) {
TcpPacket tcpPacket = ipV4Packet.get(TcpPacket.class);
if (tcpPacket != null) {
srcPort = tcpPacket.getHeader().getSrcPort().valueAsInt();
dstPort = tcpPacket.getHeader().getDstPort().valueAsInt();
}
} elseif (protocol == IpNumber.UDP) {
UdpPacket udpPacket = ipV4Packet.get(UdpPacket.class);
if (udpPacket != null) {
srcPort = udpPacket.getHeader().getSrcPort().valueAsInt();
dstPort = udpPacket.getHeader().getDstPort().valueAsInt();
}
}
// 組裝數(shù)據(jù)包信息為JSON
Map<String, Object> packetInfo = new HashMap<>();
packetInfo.put("timestamp", new Date());
packetInfo.put("srcIp", srcIp.getHostAddress());
packetInfo.put("dstIp", dstIp.getHostAddress());
packetInfo.put("protocol", protocol.name());
packetInfo.put("srcPort", srcPort);
packetInfo.put("dstPort", dstPort);
packetInfo.put("length", packet.length());
// 發(fā)送到前端
PacketWebSocketHandler.sendPacketInfo(new ObjectMapper().writeValueAsString(packetInfo));
}實(shí)時(shí)分析與展示
為了實(shí)現(xiàn)實(shí)時(shí)分析與展示,我們可以將解析后的數(shù)據(jù)包信息存儲(chǔ)到內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)(如WebSocket、隊(duì)列、列表等)中,然后通過Spring Boot的 Web功能將這些信息實(shí)時(shí)推送到前端頁(yè)面進(jìn)行展示。
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new PacketWebSocketHandler(), "/packet").setAllowedOrigins("*");
}
}編寫WebSocket處理器,將解析后的數(shù)據(jù)包信息發(fā)送給前端:
public class PacketWebSocketHandler extends TextWebSocketHandler {
private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
public static void sendPacketInfo(String info) {
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
try {
session.sendMessage(new TextMessage(info));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

























