综合运用端口匹配、深度数据包检测、流量特征进行P2P流量识别
一、使用wireshark捕获数据包
使用wireshark抓取所有数据包,抓包结束后,利用wireshark中的统计功能,统计出数据包总个数和总字节数,以备之后统计使用。
然后对数据包进行筛选,因为p2p协议只使用TCP或UDP协议,需要过滤掉那些肯定不是p2p协议的数据包,一般根据具体情况填写过滤规则,比如:“udp or tcp and not arp and not ipv6 and not nbns and not browser andnot http and not db-lsp-disc and not ssl and not rsvp and not icmpv6 and notmsproxy”表示过滤掉arp、ipv6、NBNS、BROWSER、HTTP、DB-LSP-DISC、SSL、RSVP、ICMPV6、MSPROXY协议的数据包。
然后将剩下的数据包导出为文本格式,以便于程序分析,其中会保留每个数据包的五元组信息以及十六进制数据信息。导出的文本格式数据包如下图所示:
二、数据预处理
预处理过程中会读取数据包集合文件,依次解析出每个数据包的序号、捕获时间、总字节数、数据部分字节数、源IP、目的IP、源端口、目的端口、第三层协议类型、十六进制数据部分,并依次保存。
同时还要读取从文献资料中查得的一些P2P应用程序使用的固定端口,在文本文档“Ports.txt”中;读取查得的一些P2P应用程序的应用层特征字段,在文本文档“Traits.txt”中;读取实验室主机IP地址,在文本文档“实验室主机IP.txt”中。
三、运用综合方法进行统计
方法包括:端口匹配,深度数据包检测,基于流量特征的识别方法。
针对每个数据包的信息,使用端口匹配和深度数据包检测判断是否P2P数据包,如果是则标记,以备最后统计。
重点说一下基于流量特征的识别方法:
根据提取的每个数据包的五元组(源IP、目的IP、源端口、目的端口、协议类型)信息,将数据包汇聚成流。
对于TCP数据包,流的开始时刻是其SYN标志位数据包到达时刻,流的结束时刻是其FIN或RST标志位数据包到达时刻。
对于UDP数据包,采用CiscoNetflow中的设置方式,将流的超时值设置为60s。即连续两个UDP数据包到达时间间隔超过60s则认为是两个流。
此外,每个流还要统计流中的数据包个数,流的总数据量,持续时间,流类型(上传还是下载)等信息,以便之后的基于流特征的识别方法使用。
本课程设计中用到的主要是上传和下载流量的比值特征。具体实现时,先识别出流对。方法是:若流A的源IP和源端口等于流B的目的IP和目的端口,并且流B的源IP和源端口等于流A的目的IP和目的端口,则AB为一个流对。针对流对,统计上传和下载流量,计算上传流量和下载流量的比值ratio,并设定上下行流量比值阈值,有最小阈值LowThreshhold和最大阈值UpThreshhold,ratio位于LowThreshhold和UpThreshhold之间的则认为此流对中的两个流是P2P流。
四、程序源码
#include<iostream.h>
#include<fstream>
#include<string>
#include<stdlib.h>
using namespace std;
//上下行流量比值阈值
#define LowThreshhold 0.3
#define UpThreshhold 1.8
//流结构体
typedef struct Flow
{
string SrcIP;//源IP
string DstIP;//目的IP
string SrcPort;//源端口
string DstPort;//目的端口
string Protocol;//3层协议
int pNum;//流中数据包个数
float Traffic;//此流的总数据量
float duration;//流的持续时间
int p2pFlag;//标识此流是否p2p流
int UDFlag;//标识此流是上传流还是下载流,1 上传,0 下载
}Flow;
//端口结构体
typedef struct PortsSet
{
string protocol;//协议类型,TCP或UDP
string startport;//起始端口
string endport;//终止端口
}PortsSet;
//深度数据包检测,在数据data中查找特征位trait
int DPI(string trait, string data)
{
int pos = data.find(trait);//在data中查找trait
if(pos >= 0)
return 1;
else
return 0;
}
void main()
{
int i,j,index;
float ratio=0;//上下行流量比值
int PacketsNum=0;//总数据包个数
int DPIp2pNum=0;//DPI检测到的p2p数据包个数
double DPIp2pTraffic=0;//DPI检测到的p2p数据包流量
int Portp2pNum=0;//端口匹配检测到的p2p数据包个数
double Portp2pTraffic=0;//端口匹配检测到的p2p数据包流量
int TrafficFeaturep2pNum=0;//通过流量特征检测到的p2p数据包个数
double TrafficFeaturep2pTraffic=0;//通过流量特征检测到的p2p数据包流量
int OurMethodp2pNum=0;//综合方法检测到的p2p数据包个数
double OurMethodp2pTraffic=0;//综合方法检测到的p2p数据包流量
int TraitsNum=0;//特征串个数
int PortsNum=0;//特征端口个数
int LabHostsNum=0;//实验室主机个数
string Traits[50];//特征串数组
PortsSet Ports[50];//端口数组
string LabHostsIP[200];//实验室主机IP数组
int FlowsNum=0;//流的个数
int MaxFlowPacketsNum=0;//单个流中的最大数据包个数
double MaxFlowTraffic=0;//单个流中的最大流量
Flow Flows[800];//流数组
int NewPacketFlag = 0;//标识出现新数据包
int HaveDataFlag = 0;//标识每个包是否有数据部分
int SYNFlag = 0;//标识每个包是否有SYN标识位
int FINFlag = 0;//标识每个包是否有FIN或RST标志位
int FlowNO=0;//当前数据包所属的流号
int PacketLength = 0;//每个包的总长度
int DataLength = 0;//每个包的数据部分长度
string PacketNO;//每个包的序号(帧序号)
string SrcPort,DstPort;//每个数据包的源端口和目的端口
string SrcIP ,DstIP;//每个数据包的源IP和目的IP
string Protocol;//每个数据包的协议类型
string Time;//每个数据包的捕获时间(单位是秒)
char * Buffer;//当前扫描行
Buffer = (char *)malloc(200 * sizeof(char));//分配空间
ifstream fin;//数据包文件
ifstream fin_Traits;//特征串集合文件
ifstream fin_Ports;//端口集合文件
ifstream fin_LabIP;//实验室主机IP集合文件
ofstream fout;//流信息文件
//fin.open("PacketsTest.txt");//打开数据包文件
fin.open("2012.12.16-16.40(5000个,4376755字节).txt");
fin_Traits.open("Traits.txt");//打开特征串集合文件
fin_Ports.open("Ports.txt");//打开端口集合文件
fin_LabIP.open("实验室主机IP.txt");//打开实验室主机IP集合文件
fout.open("流信息.txt");
if(!fin)
cout<<"打不开数据包文件!"<<endl;
if(!fin_Traits)
cout<<"打不开特征串集合文件!"<<endl;
if(!fin_Ports)
cout<<"打不开端口集合文件!"<<endl;
if(!fin_LabIP)
cout<<"打不开实验室主机IP集合文件!"<<endl;
if(!fout)
cout<<"无法创建流信息文件!"<<endl;
//流数组初始化
for(i=0; i<800; i++)
{
Flows[i].pNum = 0;
Flows[i].duration = 0.0f;
Flows[i].Traffic = 0;
Flows[i].p2pFlag = 0;
Flows[i].UDFlag = 1;
}
//将特征串集合文件读入到特征串数组Traits[]
for(i=0; !fin_Traits.eof(); i++)
{
TraitsNum++;
fin_Traits.getline(Buffer,30);
Traits[i] = Buffer;
//cout<<Traits[i].c_str()<<endl;
}
//cout<<"特征串个数:"<<TraitsNum<<endl;
//将端口集合文件读入到端口数组Ports[]
for(i=0; !fin_Ports.eof(); i++)
{
PortsNum++;
fin_Ports.getline(Buffer,30);
string portline(Buffer);
Ports[i].protocol = portline.substr(0,3);
portline.erase(0,4);
Ports[i].startport = portline.substr(0, portline.find(' ') );
portline.erase(0, portline.find(' ')+1 );
Ports[i].endport = portline;
//cout<<Ports[i].protocol.c_str()<<" "<<Ports[i].startport.c_str()<<" "<<Ports[i].endport.c_str()<<endl;
}
//cout<<"端口个数:"<<PortsNum<<endl;
//将实验室主机IP地址读入到数组LabHostsIP[]
for(i=0; !fin_LabIP.eof(); i++)
{
LabHostsNum++;
fin_LabIP.getline(Buffer,30);
LabHostsIP[i] = Buffer;
//cout<<LabHostsIP[i].c_str()<<endl;
}
//cout<<"实验室主机个数:"<<LabHostsNum<<endl;
//按行扫描
while(!fin.eof())
{
fin.getline(Buffer, 500);//读入一行
string line(Buffer);//转换为string类
//新数据包出现,当前行中有包序号和五元组信息
if(NewPacketFlag == 1)
{
NewPacketFlag = 0;//还原标识
PacketsNum++;//数据包个数加1
int flag = 0;//统计开始标识
//提取包序号
PacketNO = "";//清空数据包序号字符串
for(i=0; i<line.length(); i++)
{
if(line[i] != ' ')
{
flag = 1;//开始统计
PacketNO += line[i];
}
else if(flag == 1)//开始记录包序号后又出现空格,表明字段结束
{
flag = 0;//还原标志位
break;
}
}
cout<<"包序号:"<<PacketNO.c_str()<<" ";//显示
//提取数据包的捕获时间
Time = "";//清空
for(; i<line.length(); i++)
{
if(line[i] != ' ')
{
flag = 1;//开始统计
Time += line[i];
}
else if(flag == 1)//开始记录包序号后又出现空格,表明字段结束
{
flag = 0;//还原标志位
break;
}
}
cout<<"时间:"<<Time.c_str()<<" ";//显示
//提取数据包的源IP
SrcIP = "";//清空
for(; i<line.length(); i++)
{
if(line[i] != ' ')
{
flag = 1;//开始统计
SrcIP += line[i];
}
else if(flag == 1)//开始记录包序号后又出现空格,表明字段结束
{
flag = 0;//还原标志位
break;
}
}
cout<<"源IP:"<<SrcIP.c_str()<<" ";//显示
//提取数据包的目的IP
DstIP = "";//清空
for(; i<line.length(); i++)
{
if(line[i] != ' ')
{
flag = 1;//开始统计
DstIP += line[i];
}
else if(flag == 1)//开始记录包序号后又出现空格,表明字段结束
{
flag = 0;//还原标志位
break;
}
}
cout<<"目的IP:"<<DstIP.c_str()<<endl;//显示
//提取数据包的协议类型
Protocol = "";//清空
for(; i<line.length(); i++)
{
if(line[i] != ' ')
{
flag = 1;//开始统计
Protocol += line[i];
}
else if(flag == 1)//开始记录包序号后又出现空格,表明字段结束
{
flag = 0;//还原标志位
break;
}
}
cout<<"协议:"<<Protocol.c_str()<<" ";//显示
//提取源端口
index = line.find("Source port:",0);//找出源端口标识"Source port:"
if(index >= 0)//找到
{
SrcPort = line.substr(index+13,5);//取出源端口
cout<<"源端口:"<<SrcPort.c_str()<<" ";
}
//提取目的端口
index = line.find("Destination port:",0);//找出目的端口标识"Destination port:"
if(index >= 0)//找到
{
DstPort = line.substr(index+18,5);//取出目的端口
cout<<"目的端口:"<<DstPort.c_str()<<" ";
}
else//建立连接时的数据包中用">"连接源和目的端口
{
//提取源端口
SrcPort = "";//清空
for(; i<line.length(); i++)
{
if(line[i] != ' ')
{
flag = 1;//开始统计
SrcPort += line[i];
}
else if(flag == 1)//开始记录包序号后又出现空格,表明字段结束
{
flag = 0;//还原标志位
break;
}
}
cout<<"源端口:"<<SrcPort.c_str()<<" ";//显示
//提取目的端口
DstPort = "";//清空
for(i=i+2; i<line.length(); i++)
{
if(line[i] != ' ')
{
flag = 1;//开始统计
DstPort += line[i];
}
else if(flag == 1)//开始记录包序号后又出现空格,表明字段结束
{
flag = 0;//还原标志位
break;
}
}
cout<<"目的端口:"<<DstPort.c_str()<<" ";//显示
}
//汇聚为流
for(i=0; i<FlowsNum; i++)
{
if( Flows[i].SrcIP == SrcIP &&
Flows[i].DstIP == DstIP &&
Flows[i].Protocol == Protocol &&
Flows[i].SrcPort == SrcPort &&
Flows[i].DstPort == DstPort)
{
Flows[i].pNum++;//流中数据包个数加1
FlowNO = i;//当前数据包的流号
break;
}
}
if(i>=FlowsNum)//当前数据包不属于任何已有的流,增加新流
{
Flows[FlowsNum].SrcIP = SrcIP;
Flows[FlowsNum].DstIP = DstIP;
Flows[FlowsNum].Protocol = Protocol;
Flows[FlowsNum].SrcPort = SrcPort;
Flows[FlowsNum].DstPort = DstPort;
Flows[FlowsNum].pNum = 1;
FlowNO = FlowsNum;//当前数据包的流号
FlowsNum++;//流个数加1
//每新建一个流的时候进行端口匹配
for(j=0; j<PortsNum; j++)
{
if(Ports[j].protocol == Protocol)
if(SrcPort >= Ports[j].startport && SrcPort <= Ports[j].endport)
Flows[i].p2pFlag = 1;//标识此流是p2p流
}
}
}//if(NewPacketFlag == 1)
//找出包序号标识"No."的位置,找不到返回-1
index = line.find("No.",0);
if(index >= 0)//找到包序号标识"No."
{
if(HaveDataFlag == 0)//上一个包不含数据部分
cout<<"无数据部分"<<endl;
NewPacketFlag = 1;//将出现新数据包的标识置1
HaveDataFlag = 0;//将是否含数据部分的标识置0
}
//找出包长度标识"bytes on wire",找不到返回-1
index = line.find("bytes on wire",0);
if(index >= 0)//找到包长度标识
{
//cout<<index<<endl;
PacketLength = 0;//清零
string plength="";//包长度,string类型
for(i=index-2; line[i] != ' '; i--)//提取包长度
{
plength = line[i] + plength;
}
for(i=0; i<plength.length(); i++)//转换为整形
if(plength[i] >= '0' && plength[i] <= '9')
PacketLength = PacketLength * 10 + (plength[i]-'0');
cout<<"包总长度:"<<PacketLength<<"字节 ";
//累加当前数据包所属流的流量
Flows[FlowNO].Traffic += PacketLength;
}
//找出16进制数据开始标识"Data (",找不到返回-1
index = line.find("Data (",0);
//找到数据部分,开始进行数据处理
if(index >= 0)
{
HaveDataFlag = 1;//含有数据部分
//cout<<index<<endl;
DataLength = 0;//清零
string dlength = line.substr(index+6, 4);//取出数据部分长度
for(i=0; i<dlength.length(); i++)//转换为整形
if(dlength[i] >= '0' && dlength[i] <= '9')
DataLength = DataLength * 10 + (dlength[i]-'0');
cout<<"数据部分长度:"<<DataLength<<"字节"<<endl;
fin.getline(Buffer,500);//空读一行
int datalinenum = (DataLength)%16 == 0 ? (DataLength)/16 : (DataLength)/16+1;//数据部分的行数
string Data("");//去掉空格后合成的数据
//cout<<"数据部分行数:"<<datalinenum<<endl;
for(i=0; i<datalinenum; i++)
{
fin.getline(Buffer,500);
string DataLine(Buffer);//转换为string类
DataLine.erase(0,6);//去掉前面的无用数据
DataLine.erase(47);//去掉后面的ascii码
//将每行数据去掉空格后累加到Data中
for(j=0; j<DataLine.length(); j++)
{
if(DataLine[j] != ' ')
Data += DataLine[j];
}
//cout<<DataLine.c_str()<<endl;//输出每行数据
}
//cout<<Data.c_str()<<endl;//输出去掉空格后合成的数据
//对特征串集合中的每个特征进行深层数据包检测
for(i=0; i<TraitsNum; i++)
{
if( DPI(Traits[i],Data) == 1 )
{
DPIp2pNum++;//DPI检测到的p2p数据包个数加1
DPIp2pTraffic += PacketLength;//DPI检测到的p2p数据包流量
//cout<<"数据部分中含有p2p特征串"<<endl;
break;
}
}
}//找到数据部分,开始进行数据处理
}//while(!fin.eof())
//端口匹配检测到的p2p数据包个数和流量
for(i=0; i<FlowsNum; i++)
if(Flows[i].p2pFlag == 1)
{
Portp2pNum += Flows[i].pNum;
Portp2pTraffic += Flows[i].Traffic;
}
//识别每个流是上传流还是下载流
for(i=0; i<FlowsNum; i++)
for(j=0; j<LabHostsNum; j++)
if(Flows[i].SrcIP == LabHostsIP[j])
{
Flows[i].UDFlag = 1;//上传流
break;
}
else if(Flows[i].DstIP == LabHostsIP[j])
{
Flows[i].UDFlag = 0;//下载流
break;
}
//输出流信息到文件
for(i=0; i<FlowsNum; i++)
{
fout<<"流"<<i+1<<" 源IP:"<<Flows[i].SrcIP<<" 目的IP:"<<Flows[i].DstIP<<" 包个数:"<<Flows[i].pNum<<" 流量:"<<Flows[i].Traffic<<"字节 ";
if(Flows[i].UDFlag == 0)
fout<<"下载流";
else if(Flows[i].UDFlag == 1)
fout<<"上传流";
fout<<endl;
}
//流量特征检测到的p2p数据包个数和流量
for(i=0; i<FlowsNum; i++)
for(j=0; j<FlowsNum; j++)
{
if( Flows[i].SrcIP == Flows[j].DstIP && Flows[i].SrcPort == Flows[j].DstPort &&
Flows[i].DstIP == Flows[j].SrcIP && Flows[i].DstPort == Flows[j].SrcPort)//找到一对流
{
fout<<Flows[i].SrcIP.c_str()<<"---"<<Flows[i].DstIP.c_str();
//计算上下行流量比值
if(Flows[i].UDFlag == 1)
ratio = Flows[i].Traffic / Flows[j].Traffic;
else
ratio = Flows[j].Traffic / Flows[i].Traffic;
fout<<" 上下行流量比值:"<<ratio<<endl;
if(ratio >= LowThreshhold && ratio <= UpThreshhold)
{
Flows[i].p2pFlag = 1;
Flows[j].p2pFlag = 1;
TrafficFeaturep2pNum += Flows[i].pNum + Flows[j].pNum;//包个数
TrafficFeaturep2pTraffic += Flows[i].Traffic + Flows[j].Traffic;//流量
}
}
}
//统计综合方法检测到的数据包个数和流量
for(i=0; i<FlowsNum; i++)
if(Flows[i].p2pFlag == 1)
{
OurMethodp2pNum += Flows[i].pNum;
OurMethodp2pTraffic += Flows[i].Traffic;
}
cout<<endl<<endl<<"总数据包个数:"<<PacketsNum<<endl;
cout<<"流个数:"<<FlowsNum<<endl;
cout<<"通过DPI检测到的p2p数据包个数:"<<DPIp2pNum<<" 流量:"<<DPIp2pTraffic<<"字节"<<endl;
cout<<"通过端口匹配检测到的p2p数据包个数:"<<Portp2pNum<<" 流量:"<<Portp2pTraffic<<"字节"<<endl;
cout<<"通过流量特征检测到的p2p数据包个数:"<<TrafficFeaturep2pNum<<" 流量:"<<TrafficFeaturep2pTraffic<<"字节"<<endl;
cout<<"综合方法检测到的p2p数据包个数:"<<OurMethodp2pNum<<" 流量:"<<OurMethodp2pTraffic<<"字节"<<endl;
//getchar();
//关闭文件
fin.close();
fin_Traits.close();
fin_Ports.close();
fin_LabIP.close();
}//main
五、结果输出
六、结果讨论
- 1、如引言中所述,P2P数据加密使得深度数据包检测难以发挥作用,如图中所示,在4740个几乎全是P2P数据包的检测中,深度数据包检测只检测出255个数据包。
- 2、针对实验室内使用最多的P2P软件uTorrent,我们分析出了它惯用的几个端口,例如:59404、46721、62735、52768。从而使用看似可能不起作用的端口匹配方法,检测出了绝大多数uTorrent数据包。如图中所示,端口匹配方法检测出了4570个P2P数据包。
- 3、由于uTorrent软件使用过程中的上下行流量不对称,导致流量特征方法在此难以起作用。根据我们调查发现,就我们实验室而言,uTorrent软件的下载速度通常在250kb/s左右,而上传速度只能达到80kb/s左右。而程序中我们将流量特征识别方法中的上下行流量的比值设置为区间[0.3, 1.8],还是无法涵盖真实情况中的流量比。并且,还经常出现只打开uTorrent进行上传而不下载的情况,使得uTorrent数据包的上下行流量接近正无穷,因而在我们实验室的情况下,流量特征方法难以起作用。
下一篇 CAP数据包文件解析
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: