21.2.17更新:替换未渲染的plantuml流程图
前言
文章源地址:https://blog.csdn.net/pi31415926535x/article/details/107230805
学校的一个实训项目,自己根据参考代码做了一些微小的改变:将原来两个分离的两个程序:数据包的捕获和流量分析,合并,也就是用一个共享的数据包链表,并用对这两个程序用线程调用,捕获的线程将捕获到的数据包放置在链表的后面,分析的线程对链表中所有的数据包进行分析即可,简单的处理了一下共享变量间的冲突;此外,对于流量分析,通过端口来分析应用层的一些可能的协议,如43是DNS协议等等。
环境配置可以看上一篇博客,或者用IDE。。
从实训的角度,建议先看官方文档(上一篇博客),理解每一个主要的winpcap函数的作用,以及食用方法,将示例程序运行一下;然后,对于流量分析模块,直接看指导文件的参考代码,主要是那个保存链接的hash链表。
功能
- (1)实时抓取网络数据。
- (2)网络协议分析与显示。
- (3)将网络数据包聚合成数据流,以源IP、目的IP、源端口、目的端口及协议等五元组的形式存储。
- (4)计算并显示固定时间间隔内网络连接(双向流)的统计量(如上行与下行的数据包数目,上行与下行的数据量大小等)。在这些统计数据的基础上分析不同网络应用的流量特征。
代码
使用类Linux的项目管理:(vscode的配置,Linux的话makefile文件类似):
// task.json
{
"tasks": [
{
"type": "shell",
"label": "build",
// "command": "D:\\mingw64\\bin\\gcc.exe",
"command": "D:\\mingw64\\bin\\g++.exe",
"args": [
"-g",
"-std=c++17",
"-LG:\\实验报告\\实训\\WpdPack_4_1_2\\WpdPack\\Lib\\x64",
"-IG:\\实验报告\\实训\\WpdPack_4_1_2\\WpdPack\\Include",
"-finput-charset=UTF-8",
"-fexec-charset=GBK",
"${fileDirname}\\global.cpp",
"${fileDirname}\\get_devs.cpp",
"${fileDirname}\\catch_packets.cpp",
"${fileDirname}\\analysis_struct.cpp",
"${file}",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe",
"-lwsock32", // #include<winsock2.h> 使用
"-lwpcap", // #include<pcap.h> 使用
"-lws2_32", // #include<ws2tcpip.h> 使用
"-lpthread" // #include<pthread.h> 使用
],
"options": {
"cwd": "D:\\mingw64\\bin"
},
"problemMatcher": [
"$gcc"
]
}
],
"version": "2.0.0"
}
// c_cpp_properties.json
{
"configurations": [
{
"name": "Win64",
"includePath": [
"${workspaceRoot}",
"D:\\mingw64\\lib\\gcc\\x86_64-w64-mingw32\\8.1.0\\include\\",
"G:\\实验报告\\实训\\WpdPack_4_1_2\\WpdPack\\Include\\"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"browse": {
"path": [
"${workspaceRoot}",
"D:\\mingw64\\lib\\gcc\\x86_64-w64-mingw32\\8.1.0\\include\\",
"G:\\实验报告\\实训\\WpdPack_4_1_2\\WpdPack\\Include\\"
],
"limitSymbolsToIncludedHeaders": true
},
"compilerPath": "D:\\mingw64\\bin\\g++.exe",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4
}
1 获取本机设备所有的网络接口设备
同Linux对设备的解释类似,可以理解Winpcap将网卡设备解释成各种文件设备,可以使用winpcap函数包中 pcap_findalldevs_ex()
函数来获取所有的网卡设备,然后通过 pcap_open()
函数来打开指定的网卡设备,对于程序结束后,可以使用 pcap_freealldevs()
来释放所有的设备。
本项目将获取网卡设备过程分离为一个单独模块,大致流程如下:
此模块的代码文件见; get_devs.h
以及 get_devs.cpp
:
代码
#ifndef GET_DEVS
#define GET_DEVS
#include"global.h"
int get_devs(char *devs);
#endif
#include"get_devs.h"
int get_devs(char *devs){
pcap_if_t *alldevs, *d;
char errbuf[PCAP_ERRBUF_SIZE];
if(!~pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf)){
err_print("Error in pcap_findalldevs_ex(): ", errbuf);
return -1;
}
int i = 0;
for(d = alldevs; d != NULL; d = d->next){
printf("%d. %s", ++i, d->name);
if(d->description)printf(" (%s)\n", d->description);
else printf(" (No description abailable\n");
}
if(!i){
printf("\n No interface found!, Make sure winpcap is installed. \n");
return -1;
}
printf("Enter the interface number(1-%d): ", i);
int inum = i;
scanf("%d", &inum);
if(inum < 1 || inum > i){
printf("\n Interface number out of range.\n");
pcap_freealldevs(alldevs);
return -1;
}
for(d = alldevs, i = 0; i < inum - 1; d = d->next, ++i);
memcpy(devs, d->name, strlen(d->name) + 1);
dev = (pcap_if_t*)malloc(sizeof(pcap_if_t));
memcpy(dev, d, sizeof(pcap_if_t));
if((dev_fp = pcap_open(d->name, // 设备名
65536, // 要捕捉的数据包的部分
// 65535保证能捕获到不同数据链路层上的每个数据包的全部内容
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式
1000, // 读取超时时间
NULL, // 远程机器验证
errbuf // 错误缓冲池
) ) == NULL)
{
err_print("Unable to open the adapter. It is not supported by WinPcap\n", errbuf);
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
pcap_freealldevs(alldevs);
return 0;
}
2 数据包的捕获并存储
winpcap函数包中对网卡设备数据包的捕获是通过 pcap_loop
函数来实现,当其捕获到一个数据包时,就会调用作为参数的回调函数,由回调函数来处理数据包,此处定义了一个数据包链表,用于存储捕获到的所有的数据包,并作为共享数据用于后续的流量分析。数据包链表节点的大致结构为一个完成的数据包结构以及指向下一个节点的指针,回调函数的处理任务仅为将捕获的数据包添加到数据包链表后即可;此外,在开始数据包捕获时,会开启一个定时计数线程,用于在用户指定的捕获时间后进行停止捕获的作用,使用的函数是: pcap_breakloop()
来结束指定设备的捕获。最后在捕获完成后将数据包链表保存的所有数据包导出为 .pcap
格式的文件,用于其他程序的使用。此外,在进行数据包捕获前,用户可以指定数据包捕获的 过滤规则 ,如 ip
为仅捕获含有 ip 的数据包, ip and tcp
则捕获所有 TCP 协议的数据包,通过 set_pcap_filter()
函数来设定。
该模块的一个主要的流程如下:
此模块的代码文件见: catch_packets.h
和 catch_packets.cpp
。
代码
// catch_packets.h
#ifndef CATCH_PACKETS
#define CATCH_PACKETS
#include"global.h"
#include"analysis_struct.h"
int set_pcap_filter(pcap_t *dev, char *packet_filter, bpf_u_int32 mask);
// int catch_packet();
void *catch_packet(void *);
#endif
// catch_packets.cpp
#include"catch_packets.h"
// 设置过滤规则
int set_pcap_filter(pcap_t *dev, char *packet_filter, bpf_u_int32 mask){
struct bpf_program fcode;
if(!~pcap_compile(dev, &fcode, packet_filter, 1, mask)){
err_print("Unable to compile the packet filter. Check the syntax.", "");
return -1;
}
if(!~pcap_setfilter(dev, &fcode)){
err_print("Error setting the filter.", "");
return -1;
}
return 0;
}
typedef struct _argument{
pcap_t *dev;
int timeLen;
}args;
// 设定一个计时线程,当到达捕获时间时停止捕获
void *thread_clock(void *argv){
isCatchDone = false;
// win下的sleep是以毫秒计时的
Sleep((((args*)argv)->timeLen) * 1000);
pcap_breakloop(((args*)argv)->dev);
isCatchDone = true;
return NULL;
}
// 回调函数,当捕获到一个数据包时调用该函数
// 在本项目中的作用是将捕获到的数据包存储到内存中(即一个数据包链表)
void packet_handle(u_char *param, const struct pcap_pkthdr *pkthdr, const u_char *packet){
// printf("\ncatch a pkt...\n");
add_pkt(pkt_link, pkthdr, packet);
// printf("insert in pkt_link...\n\n");
// Sleep(1000);
}
void *catch_packet(void *){
printf("%s\n", dev->name);
// 检查数据链路层,仅考虑以太网
if(pcap_datalink(dev_fp)