FTP深度包检测——ndpi源码分析

一、简介

       在本文中,我们将看到ndpi提供的的分析函数ndpi_detection_process_packet的主要实现。以及ndpi内部是如何对FTP协议实现深度包检测技术。对于前期的初始化和API的使用,请参考附录中的文章。欢迎大家一起讨论学习。然后我们这次会带着下面几个问题阅读源代码:

         1、ndpi的分析函数进行了怎么样的工作?

         2、ndpi内部是如何实现深度包检测技术的?

         3、ndpi里面,对于多种协议的检测是基于怎样的准则进行选择?

注意:因为限于篇幅需要下面进行分析默认针对ipv4,如果有不正确的地方欢迎大家一起来讨论学习

二、分析函数API的实现  

       ndpi内部提供了ndpi_detection_process_packet(在ndpi_main.c中进行了定义)函数作为协议检测的API,函数原型及解释如下:      

       

       在ndpi_detection_process_packet函数中,我们根据参数中的ndpi_struct、packet、current_tick、src、dst对flow完成一系列的初始化工作。在其内部维护一个针对会话的协议栈,然后根据多次的检测选择最优的匹配的应用层协议。具体动作细节如下:
       1、检测包长度
        这里它通过检测包长度(packetlen),对包的可用性进行了测试。如果包长度没有20字节(ip数据报文至少20字节),则利用ndpi_int_reset_packet_protocol把flow内部的协议栈顶类型置为UNKNOW。并且清0协议栈信息字段,最后返回UNKNOW类型。这里关于flow协议栈的结构和使用我们将在第三章的FTP协议检测中详细进行介绍。
        2、flow->packet的初始化
        这里通过捕获的ip报文(packet参数)和用户设置的心跳值(current_tick参数),对flow->packet.iph和flow->packet.tick_timestamp进行初始化。
        3、传输层检测及flow的初始化
        这里主要通过函数ndpi_init_packet_header(在ndpi_main.c中进行了定义)进行了实现,他完成比较多的工作。
        1)首先一个就是根据ndpi_packet_struct中的协议栈内容和描述信息,对flow的协议栈内容和描述信息进行了初始化。这部分通过内部的ndpi_apply_flow_protocol_to_packet函数进行了实现。
        2)根据ipv4和ipv6对flow中的packet分别进行初始化(flow->packet.iph和flow->packet.iphv6)
        3)通过ndpi_detection_get_l4_internal对报文的ipv4(ipv6)header进行检测,并且获取传输层协议信息。通过l4protocol变量进行传递,记录传输层协议号。
        4)根据l4protocol字段进行传输层的判别,如果是tcp(协议号是6)则对包内部的syn和ack等字段进行初步的检测。如果是udp(协议号是17),则计算出包的长度。初始化flow->packet当中的字段
        4、flow传输层信息的初始化
        这里主要通过报文获取传输层信息,比如在tcp协议中我们捕获到的报文是握手中的什么角色,是ack包还是其他的。这些信息将对检测提供一些数据。
        1)首先通过src和dst参数初始化flow->src和flow->dst字段
        2)通过ndpi_connection_tracking函数进行我们上述的工作。这里它判断的tcp握手的状态,并且通过flow->next_tcp_seq_nr数组对tcp序列进行了描述。
        我们这里拿一段代码来一起进行分析。看看具体是怎样操作的。 
       
           上面这段代码中,我们不难看出ndpi中通过flow->l4.tcp中的seen_syn、seen_syn_ack和seen_ack记录tcp的握手状态。然后根据分析报文中的syn和ack字段进行归类,为后期的检测提供数据基础。
         5、建立传输层的描述
         这一部分通过ndpi_selection_packet进行记录,主要是提供给下面第6部分一个传输层上分类的检测。这里的ndpi_selection_packet将记录传输层的信息。包括是tcp还是udp,里面还检测不出来时的灰色选项(tcp or udp)。数据结构这里通过一个整形进行记录,大家还有不明白可以参考附录中的ndpi协议的注册及维护的文章。里面第三章的第一点详细讲述了该方法。
         6、调用对应的检测函数
         这里ndpi根据第5点提供的参考,对捕获的数据包进行了传输层的分类。分成tcp、udp和none tcp&&udp(既不是tcp也不是udp)。然后对于每个类别,调用其对用的注册函数。
         注:关于检测协议函数的注册,我们在协议的注册与维护一文中进行了详细的介绍。具体请参考附录2
         我们这里列一段代码来一起看看他的具体操作是怎么样的:
         

三、FTP深度包检测的实现

          这里我们将介绍一下,ndpi里面FTP的深度深度包检测是怎么实现。通过上述的第二章第6点的func调用,我们在ndpi_detection_process_packet函数内部的tcp传输层类别调用FTP的检测函数ndpi_search_ftp_tcp。具体定义在<ndpi home>/src/lib/protocol/中,里面囊括了ndpi支持的所有协议的源代码。我们马上一起来看看ndpi是怎么样完成FTP的深度包检测的。这一部分我们分为两part进行介绍,具体如下:
       1、源和目的ip及端口检测
        这里首先是两个分别针对源端口和目的端口的if语句检查,这里应该是考虑到FTP请求发起方的问题。检查内容包括:
        1)src非空
        2)ip报文的源ip地址和src中的FTP地址是否匹配
        3)协议栈顶不等于UNKNOW类型
        4)比较src中的检测协议映射是否包含FTP协议
        5)src中ftp_timer的设置非0
        然后完成上面的工作之后,如果会话的目的端口号大于1024,并且源端口号大于1024或等于20。则判定这个会话为FTP会话,通过ndpi_int_ftp_add_connection(这个函数在ftp.c中定义和实现,具体实现的函数在ndpi_main.c中抽象出来和其他检测协议共用)修改协议栈。这里因为调用关系比较多,我们不列代码了。我们将在下面第2部分,针对栈的实现和操作进行介绍。
        2、检测协议栈的操作
        ndpi内部的ndpi_flow_struct中通过数组和自定义的栈描述描述信息,实现了深度检测的栈结构。其实属性Linux内核的人会发现,这个结果和进程内核栈的实现是非常相似的。只是比较简单,没有了想thread_info那样的索引。栈本身是通过detected_protocol_stack数组进行实现,然后在protocol_stack_info字段中定义了两个信息来描述这个栈。一个是entry_is_real_protocol,它描述的是栈中是否检测出了正确的协议。第二个是current_stack_size_minus_one,它描述的是栈的大小。detected_protocol_stack栈中的是从高地址开始的,也就是detected_protocol_stack[0]代表的是栈顶。current_stack_size_minus_one表示的栈底部。具体定义如下:
       
       对栈的操作,并没有进行封装。直接在各自的功能函数中进行实现,以上面的添加协议为例,将最终在ndpi_int_change_flow_protocol(ndpi_main.c中实现)进行操作。这里的函数是在FTP的检测函数ndpi_search_ftp_tcp内部通过ndpi_int_ftp_add_connection协议操作函数,最终在ndpi_main.c中得到调用和实现。具体的调用过程这里省略,有兴趣的朋友可以联系我。我们这里列一段ndpi_int_change_flow_protocol的栈操作代码,其实并没有我们相信中那么难。
       
       上面这几行代码,实现了栈的滑动和插入过程。没有想象中那么难吧。
        
      3、FTP主动模式和被动模式的检测
      这里我们需要补充一点背景知识:
      FTP主动模式:由服务器端发FTP请求
      FTP被动模式:由客户端发送FTP请求
      在ndpi_search_ftp_tcp中,ndpi通过search_passive_ftp_mode和search_active_ftp_mode调用并进行实现。这里因这两个函数原理差不多,我们针对search_passive_ftp_mode进行介绍。这里主要采用的方法是对捕获的FTP数据报文进行搜索,包搜索的实现是通过memcmp函数进行实现,这个是公共库中的函数。在#include <string.h>或#include<memory.h>中,我们可以获得这个函数。函数原型如下:
      int memcmp(const void *buf1, const void *buf2, unsigned int count);
当buf1<buf2时,返回值<0
当buf1=buf2时,返回值=0
当buf1>buf2时,返回值>0
然后工具有了,接下来就是利用这个函数对捕获的包进行比较。看看有没有被动模式包含的字段,这里我们再列一段代码一起看看:
上述代码中,通过if中的判断如果包长度大于34,并且能匹配到229 Entering Extended Passive Mode。则进入判断分别对src和dst进行非空的测试,最后通过NDPI_LOG记录进去日志中。注:这里的src和dst并不是指源端口,而是上文中描述的ndpi_id_struct结构。

四、附录

     附录一:ndpi网站(源代码用svn详细见网站介绍):
                           http://www.ntop.org/products/ndpi/

     附录二:协议的注册与维护——ndpi源码分析
                          http://blog.csdn.net/grublinux/article/details/37670619

     附录三:pcapreader——源码分析
                          http://blog.csdn.net/grublinux/article/details/31603915

        最后谢谢在这个过程中,williamy给予我的帮助。




你可能感兴趣的:(源码分析,移动安全,ndpi,深度包分析)