pcapReader——源码分析

一、简介 

       pcapReader是ndpi开源中的一个example。大家可以从<ndpi directory>/example/pcapReader.c中找到它的源代码。通过pcaplib和ndpi相结合,进行深度包检测。虽然只有短短的几行代码,但是他将展现的不仅是pcaplib和ndpi的使用方法,还有包分析的一些技巧。看完之后其实外国人写的程序也就是那样,并没有什么特别之处。我们先来一起看看基本的函数结构。

        注:我们只对源码中的Linux平台部分进行解释

         在main函数中,通过调用test_lib()对程序进行整合。   

         
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
           
           
void test_lib () {
struct timeval begin , end ;
u_int64_t tot_usec ;
setupDetection (); //ndpi检测协议的注册,以及参数设置
openPcapFileOrDevice (); //pcaplib的初始化准备
signal ( SIGINT , sigproc ); //包含在signal.h头文件中,这里主要交互式信号,如中断做出反应。触发sigproc函数关闭程序
gettimeofday ( & begin , NULL ); //记录开始时间
runPcapLoop (); //循环抓包并进行处理
gettimeofday ( & end , NULL ); //记录结束时间
//计算抓包分析耗时
tot_usec = end . tv_sec * 1000000 + end . tv_usec - ( begin . tv_sec * 1000000 + begin . tv_usec );
closePcapFile (); //关闭网卡
printResults ( tot_usec ); //输出结果
terminateDetection (); //关闭ndpi
}
 来自CODE的代码片
pcapReader_test_lib.c
      这里限于篇幅,主要对runPcapLoop()函数中的动作进行分析。如果想理解其他函数或者更加详细的技术细节,可以阅读博客最后的源码附录。里面有比较详细的注释。如果还有问题,可以留言或者发一下私信。欢迎大家一起讨论。

二、包分析

        runPcapLoop()函数中通过pcap_loop(_pcap_handle, -1, &pcap_packet_callback, NULL)进行循环抓包。pcap_loop是pcaplib中提供的api。_pcap_handle指向的是网卡设备,pcap_packet_callback是循环抓包之后的包处理函数,-1代表的是不停地抓直到抓包出错的时候停止。接下来我们针对pcap_packet_callback函数中的包处理进行分析

1)pcap_packet_callback函数

pcap_packet_callback函数中,按顺序分成4个主要部分:
         1、ndpi_ethhdr进行数据链路层的拆包分析。针对Linux Cooked Capture 和vlan的特殊包结构。对包头和信息进行了对应的偏移,并且记录在ip_offset变量中。
         2、ndpi_iphdr进行网络层的拆包。这里进行了ipv4和ipv6的检测,我们接下来只对ipv4进行介绍。
         3、GTP隧道协议的处理
         4、packet_processing()函数进一步的包处理
         注:2中的网络层拆包存储在iph变量中,并在packet_processing()中作为ndpi协议检测的数据源
          
packet_processing函数作为ndpi分析的主体,这里通过get_ndpi_flow函数分类会话。然后利用ndpi_detection_process_packet函数进行数据分析得到应用层协议。我们继续往下看看get_ndpi_flow是怎样建立起数据结构的。
       注:get_ndpi_flow6针对ipv6进行了转换,最后还是通过get_ndpi_flow建立

2)get_ndpi_flow函数

get_ndpi_flow函数:
1、通过传输层拆包获得协议包的源和目的端口(tcp通过ndpi_tcphdr 、udp通过ndpi_udphdr分别进行拆包)
2、结合网络层和传输层的数据,通过源目的ip和端口分类会话
3、以ndpi_flows_root为hash数组,(lower_ip + upper_ip + iph->protocol + lower_port + upper_port) % NUM_ROOTS计算出会话对应的数组位置。然后对于数组的每个单元维护一个二叉查找链表。
4、通过ndpi_tfind函数对二叉树进行查找,如果存在相对应的会话,则返回对应结果。如果不存在,则通过ndpi_tsearch把新的会话插入二叉树中。
node_cmp函数中定义了比较的规则。ndpi_tfind和ndpi_tsearch在<ndpi directory>/src/lib/ndpi_main.c文件中进行的二叉查找的封装。
  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
           
           
typedef struct node_t {
char * key ;
struct node_t * left , * right ;
} ndpi_node ;
/* find a node, or return 0 */
void * ndpi_tfind ( const void * vkey , void * vrootp , int ( * compar )( const void * , const void * ))
{
char * key = ( char * ) vkey ;
ndpi_node ** rootp = ( ndpi_node ** ) vrootp ;
if ( rootp == ( ndpi_node ** ) 0 )
return (( ndpi_node * ) 0 );
while ( * rootp != ( ndpi_node * ) 0 ) { /* T1: */
int r ;
if (( r = ( * compar )( key , ( * rootp ) -> key )) == 0 ) /* T2: */
return ( * rootp ); /* key found */
rootp = ( r < 0 ) ?
& ( * rootp ) -> left : /* T3: follow left branch */
& ( * rootp ) -> right ; /* T4: follow right branch */
}
return ( ndpi_node * ) 0 ;
}
void * ndpi_tsearch ( const void * vkey , void ** vrootp , int ( * compar )( const void * , const void * ))
{
ndpi_node * q ;
char * key = ( char * ) vkey ;
ndpi_node ** rootp = ( ndpi_node ** ) vrootp ;
if ( rootp == ( ndpi_node ** ) 0 )
return (( void * ) 0 );
while ( * rootp != ( ndpi_node * ) 0 ) { /* Knuth's T1: */
int r ;
if (( r = ( * compar )( key , ( * rootp ) -> key )) == 0 ) /* T2: */
return (( void * ) * rootp ); /* we found it! */
rootp = ( r < 0 ) ?
& ( * rootp ) -> left : /* T3: follow left branch */
& ( * rootp ) -> right ; /* T4: follow right branch */
}
q = ( ndpi_node * ) ndpi_malloc ( sizeof ( ndpi_node )); /* T5: key not found */
if ( q != ( ndpi_node * ) 0 ) { /* make new node */
* rootp = q ; /* link new node to old */
q -> key = key ; /* initialize new node */
q -> left = q -> right = ( ndpi_node * ) 0 ;
}
return (( void * ) q );
}
 来自CODE的代码片
BinarySearchTree.c
         

3)整体数据结构

          

三、其他函数

1、setupDetection();//ndpi检测协议的注册,以及参数设置
      通过ndpi提供的一系列函数,注册需要深度检测的协议。大略如下
        ndpi_init_detection_module激活cache支持,主要针对一些占用缓存的协议如skype
        ndpi_set_protocol_detection_bitmask2注册需要进行检测的协议
        ndpi_detection_get_sizeof_ndpi_id_struct
        ndpi_detection_get_sizeof_ndpi_flow_struct:获取ndpi_flow_struct和ndpi_id_struct的大小在为二叉树插入新节点时,申请空间和变量的初始化
        
2、openPcapFileOrDevice();//pcaplib的初始化准备
     errbuf[PCAP_ERRBUF_SIZE]:pcaplib存放错误信息的缓冲区
     pcap_open_live打开对应的网卡设备
        注:如果打开失败,或者命令中指定利用pcap_open_offline从文件中读入数据
              pcap_datalink获取当前数据链路的类型,一般为以太网v2
              pcap_compile和pcap_setfilter分别用于编译和设置抓包的过滤规则

3、signal(SIGINT, sigproc);//包含在signal.h头文件中,这里主要交互式信号,如中断做出反应。触发sigproc函数关闭程序   
 1
 2
 3
 4
 5
 6
 7
 8
 9
            
            
void sigproc ( int sig ) {
static int called = 0 ;
if ( called ) return ; else called = 1 ;
shutdown_app = 1 ;
closePcapFile ();
printResults ( 0 );
terminateDetection ();
exit ( 0 );
}
 来自CODE的代码片
pcapReader_signal.c
如果产生中断,则调用如上函数关闭pcap和ndpi并且输出结果。

4、closePcapFile();
通过pcap_close函数清除_pcap_handle指针并关闭抓包。

5、printResults(tot_usec);//输出结果

6、terminateDetection();
通过ndpi_tdestroy释放hash数组及其数组上的二叉查找树节点,最后通过ndpi_exit_detection_module结束ndpi程序。

7、static void parseOptions(int argc, char **argv)   /*命令行的实现,这里argc和argv从main中argc和argv参数传递进来。*/
 getopt函数是命令行分析 第三个参数解释:
     1).单个字符,表示选项
     2).单个字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。该参数的指针赋给optarg。
     3).单个字符后跟两个冒号,表示该选项后可以跟一个参数,也可以不跟。如果跟一个参数,参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给optarg。
getopt中选项得到的参数传递给全局变量optarg

四、源码附录

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值