NDPI的分析

简单的说,首先抓包,再从数据链路层开始解析,一直解析到传输层是TCP或是UDP或是TCP和UDP都不是,最后才到应用层,到应用层只能依靠端口,分析包的内容来提取特征码等等来判断是何种协议类型。

一、     重要的数据结构

1、        整个pcap包的信息

struct reader_thread {

  struct ndpi_detection_module_struct*ndpi_struct;

 void *ndpi_flows_root[NUM_ROOTS];

 char _pcap_error_buffer[PCAP_ERRBUF_SIZE];

 pcap_t *_pcap_handle;

 u_int64_t last_time;

 u_int64_t last_idle_scan_time;

 u_int32_t idle_scan_idx;

 u_int32_t num_idle_flows;

 pthread_t pthread;

  int_pcap_datalink_type;

  /*TODO Add barrier */

  structthread_stats stats;

 struct ndpi_flow *idle_flows[IDLE_SCAN_BUDGET];

};

 

2、        核心数据结构ndpi_detection_module_struct,贯穿始终。

typedef struct ndpi_detection_module_struct{

 NDPI_PROTOCOL_BITMASK detection_bitmask;

 NDPI_PROTOCOL_BITMASK generic_http_packet_bitmask;

 u_int32_t current_ts;

 u_int32_t ticks_per_second;

#ifdef NDPI_ENABLE_DEBUG_MESSAGES

 void *user_data;

#endif

  /* callback function buffer *//*为所有协议绑定具体的处理函数*/

  structndpi_call_function_struct callback_buffer[NDPI_MAX_SUPPORTED_PROTOCOLS+ 1];

  u_int32_tcallback_buffer_size;

/* 根据NDPI_PROTOCOL_BITMASK再细分成tcp_no_payload、tcp_payload 、udp、non_tcp_udp */

 struct ndpi_call_function_struct callback_buffer_tcp_no_payload[NDPI_MAX_SUPPORTED_PROTOCOLS+ 1];

 u_int32_t callback_buffer_size_tcp_no_payload;

 struct ndpi_call_function_struct callback_buffer_tcp_payload[NDPI_MAX_SUPPORTED_PROTOCOLS+ 1];

 u_int32_t callback_buffer_size_tcp_payload;

 struct ndpi_call_function_struct callback_buffer_udp[NDPI_MAX_SUPPORTED_PROTOCOLS+ 1];

 u_int32_t callback_buffer_size_udp;

 struct ndpi_call_function_struct callback_buffer_non_tcp_udp[NDPI_MAX_SUPPORTED_PROTOCOLS+ 1];

 u_int32_t callback_buffer_size_non_tcp_udp;

 

  ndpi_default_ports_tree_node_t *tcpRoot,*udpRoot;/*默认port的tree*/

 

#ifdef NDPI_ENABLE_DEBUG_MESSAGES

  /*debug callback, only set when debug is used */

 ndpi_debug_function_ptr ndpi_debug_printf;

 const char *ndpi_debug_print_file;

 const char *ndpi_debug_print_function;

 u_int32_t ndpi_debug_print_line;

#endif

 

  /*misc parameters */

 u_int32_t tcp_max_retransmission_window_size;

 u_int32_t directconnect_connection_ip_tick_timeout;

 

  /*subprotocol registration handler */

 struct ndpi_subprotocol_conf_structsubprotocol_conf[NDPI_MAX_SUPPORTED_PROTOCOLS + 1];

 

 u_int ndpi_num_supported_protocols;

 u_int ndpi_num_custom_protocols;

 

  /*HTTP/DNS/HTTPS host matching */ /*此处在ndpi_content_match.c和proto.txt中定义*/

 ndpi_automa host_automa,       /* Used for DNS/HTTPS */  /*域名匹配*/

/*内容类型eg:NDPI_CONTENT_MPEG*/

content_automa,                         /*Used for HTTP subprotocol_detection */   

/*文档自定义proto.txt的匹配*/

subprotocol_automa,          /* Used for HTTPsubprotocol_detection */

/*双字节的匹配*/

    bigrams_automa, impossible_bigrams_automa;/* TOR */

  /*IP-based protocol detection */

  void *protocols_ptree;                                           /*此是ip匹配的tree*/

 

  u_int32_t irc_timeout;                                          /*irc parameters */

 u_int32_t gnutella_timeout;                              /* gnutella parameters*/

  u_int32_tbattlefield_timeout;                           /* battlefieldparameters */

 u_int32_t thunder_timeout;                              /* thunder parameters*/

 u_int32_t soulseek_connection_ip_tick_timeout;   /*SoulSeek parameters */

  u_int32_t rtsp_connection_timeout;                /*rtsp parameters */

 u_int32_t tvants_connection_timeout;           /* tvants parameters */

 u_int32_t orb_rstp_ts_timeout;                        /*rstp */

 u_int8_t yahoo_detect_http_connections;  /* yahoo */  

 u_int32_t yahoo_lan_video_timeout;

 u_int32_t zattoo_connection_timeout;

 u_int32_t jabber_stun_timeout;

 u_int32_t jabber_file_transfer_timeout;

#ifdef NDPI_ENABLE_DEBUG_MESSAGES

#define NDPI_IP_STRING_SIZE 40

 char ip_string[NDPI_IP_STRING_SIZE];

#endif

 u_int8_t ip_version_limit;

#ifdef NDPI_PROTOCOL_BITTORRENT

 struct hash_ip4p_table *bt_ht;

#ifdef NDPI_DETECTION_SUPPORT_IPV6

 struct hash_ip4p_table *bt6_ht;

#endif

#ifdef BT_ANNOUNCE

 struct bt_announce *bt_ann;

 int    bt_ann_len;

#endif

#endif

/**/

ndpi_proto_defaults_tproto_defaults[NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS];

 

 u_int8_t match_dns_host_names:1, http_dont_dissect_response:1;

 u_int8_t direction_detect_disable:1; /* disable internal detection ofpacket direction */

} ndpi_detection_module_struct_t;

 

二、     重要的函数:

1、       main函数有以下几个,针对每个简单介绍:

(1)       parseOptions(argc, argv); 处理给定的参数。这里参数很多,可以通过-h来查看(具体可在static void help(u_int long_help)函数里查看)。

 

(2)       setupDetection(thread_id); 非常重要的初始化函数,其会初始化整个程序的最重要的数据结构:ndpi_detection_module_struct_t,所有协议的回调函数都是在这里进行绑定的。里面重要的函数:

另外还会的给几个全局变量分配内存,

ndpi_id_struct:所有id对应的变量,每个id用来连接一个IP与一个表示该IP的ID。

ndpi_flow_struct:所有flow对应的变量,每个flows用来连接一个flow与一个表示该flow的KEY。

 

(3)       openPcapFileOrDevice(thread_id);打开pcap文件(pcap_open_offline)或者打开网口开始抓包(pcap_open_live),在通过pcap_datalink得到返回数据链路层类型(例如DLT_EN10MB),若是想要设置包的过滤规则,可在此处通过pcap_compile、pcap_setfilter来设置。

 

(4)       pthread_create(&ndpi_thread_info[thread_id].pthread,NULL, processing_thread, (void *) thread_id); 为每个thread_id创建线程,并指明处理的函数为processing_thread。处理pcap的主循环,其最终通过回调函数来调用各个协议的分析函数。

 

(5)       pthread_join(ndpi_thread_info[thread_id].pthread, NULL);等待每个thread_id的线程结束。当函数返回时,被等待线程的资源被收回。

 

(6)       printResults(tot_usec); 输出统计结果。

 

(7)       closePcapFile(thread_id); 关闭pcap文件。

 

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

 

2、        setupDetection(thread_id):setupDetection ->ndpi_set_protocol_detection_bitmask2 -> ndpi_set_bitmask_protocol_detection

ndpi_init_detection_module(detection_tick_resolution, malloc_wrapper, free_wrapper,debug_printf)

返回值:一个ndpi_detection_module_struct的指针,其指向的对象为该程序的核心数据结构。整个程序有且只有一个该结构。

参数1:detection_tick_resolution 这里的实参为1000,指每秒的时钟滴答(具体干什么用的不清楚)

参数2:malloc_wrapper,指定的用来分配内存的类似于malloc的函数

参数3:free_wrapper,指定的用来释放内存的类似于free的函数

参数4:debug_printf,指定的用来进行debug下的print的函数

总的来说,该函数就是用malloc函数分配了一个ndpi_detection_module_struct,该结构体中的许多成员被设定成了默认值,并返回指向该结构体的指针。

(1)      先用malloc函数分配了一个ndpi_detection_module_struct,该结构体中的许多成员被设定成了默认值。

(2)      Network host的初始化(存在host_protocol_list数组里:将host_protocol_list(依据ip来match的)建立成tree,并用ndpi_detection_module_struct->protocols_ptree指向它。

(3)      默认端口的初始化:主要作用是维护一个二叉树型结构,用来记录各个协议的默认端口。

ndpi_build_default_ports操作比较简单,只是把参数中的端口号传递进去ports_a/ports_b并返回。

将协议的基本信息(名字和ID)存进ndpi_mod->proto_defaults。udpDefPorts和tcpDefPorts是之前赋值的prots_a/ports_b。addDefaultPort函数中新增ndpi_default_ports_tree_node_t结构体作为二叉树的节点。这个结构包含了原来的ndpi_proto_defaults_t,再附加了默认端口号。然后按照传输层分类,分别挂到udpRoot和tcpRoot中。这里二叉查找插入通过ndpi_tsearch进行实现。

注意,该函数并未让该结构体绑定回调函数。

(4)host中url(定义的是host_match[]数组)和content(定义的是content_match[]数组)的初始化。然后判断是不是supported_protocols是不是均已正确的被初始化。初始化分成4个:

A.将ndpi_mod->host_automa(对应host_match[]数组)的初始化:

注意:host_match数组属于supported的protocol,故还要在ndpi->proto_default初始化下。

B.其他ndpi_mod->content_automa(对应content_match[ ]数组)、ndpi_mod->bigrams_automa(对应ndpi_en_bigrams[]数组)、ndpi_mod->impossible_ bigrams_automa(对应impossible_bigrams_automa[ ]数组)初始化类似。

至此ndpi结构体初始化完毕。

NDPI_BITMASK_SET_ALL(all)NDPI_PROTOCOL_BITMASK代表的是一个变量类型,而all则是一个定义处理的实例(变量)。详细的定义在ndpi_macros.h中,NDPI_PROTOCOL_BITMASK类型就是一个u_int32_t的数组,如下:

    NDPI_BITMASK_SET_ALL(all)就是把映射中所有的应用都进行设置。

 

ndpi_set_protocol_detection_bitmask2(ndpi_thread_info[thread_id].ndpi_struct,&all)

是检测协议注册的核心函数。

(1)   设置所有标志位能使:

 

2)一个size

 

这里的callback_buffer_size是指的回调函数的个数,为了安全起见我们现在先设置其为0

 

3)然后注册每个protocol,以http为例说明:

其中调用了一个函数:

 

Lable:是protocol的名字。

Ndpi_struct:全局结构体。

Detection_bitmask:是检测protocol是否要探测。

Idx:注册的个数。

Ndpi_protocol_id:define的protocol的id。

Func:回调的函数。

Ndpi_selection_bitmask:该protocol的标识位。

B_save_bitmask_unknow:

B_add_detection_bitmask:

对此函数详细说明, 此函数就一个if,不可用就什么操作也不做:

A.此Ndpi_protocol_id要是可用,判断proto_default[ndpi_protocol_id].protoIdx若不为0,则说明已注册。

B. proto_default[ndpi_protocol_id].func设置回调的函数。

C. ndpi_struct->callback_buffer[idx]设置该protocol的标识ndpi_selection_bitmask(http是NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD)。

D.将ndpi_struct->callback_buffer[idx]的protocol的detection_bitmask和excluded_protocol_bitmask均设为该protocol_id。

(4)callback_buffer总归有点乱,再细分下。于是这里根据不同的bitmask把callback_buffer又细分成了四个函数,最终我们可以看到,在runPcapLoop中调用的也只是这四个函数罢了。Callback_buffer已经可以功成身退了。

l  为id、flow分配memory。

 

 

l  读取自定义的custom protocol文件。(待完善)

 

3、        包分析流程:processing_thread -> runPcapLoop ->pcap_packet_callback -> packet_processing

processing_thread:里面就一个核心runPcapLoop(thread_id)。

runPcapLoop:主要是对每个_pcap_handle执行pcap_loop具体如下:

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

pcap_packet_callback(得到l2,再得到l3):此函数中,按顺序分成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:packet_processing函数作为ndpi分析的主体,这里通过get_ndpi_flow函数分类会话。然后利用ndpi_detection_process_packet函数进行数据分析得到应用层协议。我们继续往下看看get_ndpi_flow是怎样建立起数据结构的。

注:get_ndpi_flow6针对ipv6进行了转换,最后还是通过get_ndpi_flow建立。

1、flow的获取,是在packet_processing 获取得到的,

flow = get_ndpi_flow

ndpi_flow = flow->ndpi_flow

同一条流的判断:源地址,目的地址,源端口,目的端口,协议类型

  idx = (lower_ip + upper_ip +iph->protocol + lower_port + upper_port) % NUM_ROOTS

每个包都会对应一条流,直至流的数量大于等于 200000000。

1)通过iph->protocol是tcp或udp类别来获得l4,然后通过传输层拆包获得协议包的源和目的端口(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_tfind(&flow,&ndpi_thread_info[thread_id].ndpi_flows_root[idx],node_cmp);)对二叉树进行查找,如果存在相对应的会话,则返回对应结果。如果不存在,则通过ndpi_tsearch(ndpi_tsearch(newflow,&ndpi_thread_info[thread_id].ndpi_flows_root[idx],node_cmp); /* Add */)把新的会话插入二叉树中。

 

 

整体数据结构:

2、每种应用层协议的检测的流程:

ndpi_detection_process_packet -> check_ndpi_flow_func ->check_ndpi_tcp_flow_func(check_ndpi_udp_flow_func或check_ndpi_other_flow_func) -> ndpi_struct->callback_buffer_tcp_payload[a].func(ndpi_struct,flow) 

ndpi_struct->callback_buffer_tcp_payload[a].func(ndpi_struct,flow)  就到了每个具体的协议检测,比如ndpi_search_http_tcp。

1)staticint ndpi_init_packet_header(structndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow,unsigned short packetlen)

初始化了ipoque_struct->packet这个成员,得到l4和payload,并把flow中的一些信息复制到了该成员中。

2)ndpi_connection_tracking(ndpi_struct,flow):设置ndpi_struct中的packet和flow的状态,有很大一部分代码和tcp的状态有关。

 

3)设置ndpi_selction packet  bitmask,该标志位来源的依据主要来自之前对于flow和packet的分析(ipoque_connection_tracking)。换句话说,该标志位表明这个packet拥有何种属性(如是否为IP?是否为IPV4?是否有PAYLOAD?)

4)调用ndpi_guess_protocol_id,据protocol、sport、dport进行协议猜测。

5)调用check_ndpi_flow_func(ndpi_struct, flow, &ndpi_selection_packet)据是tcp(调tcp_payload和tcp_nopayload)还是udp(调udp)来进行不同的调用,在check_ndpi_tcp_flow_func根据不同的标识位进行func的回调。

 

6)若是经过以上未确定检测到是何种protocol,则据saddr、daddr检测是何种protocol。

 

三、重要的逻辑:

ndpi_protocolndpi_guess_undetected_protocol(struct ndpi_detection_module_struct*ndpi_struct,    u_int8_t proto,   u_int32_t shost /* host byte order */,   u_int16_t sport, u_int32_t dhost /* hostbyte order */,    u_int16_t dport)

l  proto是tcp或udp时,

(1)      Guess的时候,先据saddr、daddr去ndpi_struct->protocols_ptree里match;

(2)      再据sport和dport去ndpi_struct->tcpRoot(proto == IPPROTO_TCP时)或ndpi_struct->udpRoot(proto== IPPROTO_UDP时)去compare得到的protocol赋给ret.master_protocol;

(3)      (1)(2)均未match,则根据saddr、daddr、sprot、dport一起去判断是不是protocol是不是SKYFILE;

l  proto非udp和tcp时,据以下几种定protocol。

#defineNDPI_IPSEC_PROTOCOL_ESP          50

#defineNDPI_IPSEC_PROTOCOL_AH           51

#defineNDPI_GRE_PROTOCOL_TYPE          0x2F

#define NDPI_ICMP_PROTOCOL_TYPE       0x01

#defineNDPI_IGMP_PROTOCOL_TYPE        0x02

#defineNDPI_EGP_PROTOCOL_TYPE           0x08

#defineNDPI_OSPF_PROTOCOL_TYPE         0x59

#defineNDPI_SCTP_PROTOCOL_TYPE         132

#defineNDPI_IPIP_PROTOCOL_TYPE    0x04

#define NDPI_ICMPV6_PROTOCOL_TYPE  0x3a

返回对应的是:

#define NDPI_PROTOCOL_IP_VRRP                                   73

#define NDPI_PROTOCOL_IP_IPSEC                                         79

#define NDPI_PROTOCOL_IP_GRE                                            80

#define NDPI_PROTOCOL_IP_ICMP                                         81

#define NDPI_PROTOCOL_IP_IGMP                                         82

#define NDPI_PROTOCOL_IP_EGP                                            83

#define NDPI_PROTOCOL_IP_SCTP                                          84

#define NDPI_PROTOCOL_IP_OSPF                                          85

#define NDPI_PROTOCOL_IP_IP_IN_IP                                   86

#defineNDPI_PROTOCOL_IP_ICMPV6                                    102

 

 

四、关于port的相关:

ndpi_main.c:

771行:static voidndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndpi_mod)

 

1895行:u_int16_t ndpi_guess_protocol_id(struct ndpi_detection_module_struct*ndpi_struct,    u_int8_t proto,u_int16_t sport, u_int16_t dport);

 

sport和dport去ndpi_struct->tcpRoot(proto== IPPROTO_TCP时)或ndpi_struct->udpRoot(proto == IPPROTO_UDP时)去compare得到的protocol赋给ret.master_protocol;

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值