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

l  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結構體初始化完畢。

l  NDPI_BITMASK_SET_ALL(all):NDPI_PROTOCOL_BITMASK代表的是一個變量類型,而all則是一個定義處理的實例(變量)。詳細的定義在ndpi_macros.h中,NDPI_PROTOCOL_BITMASK類型就是一個u_int32_t的數組,如下:

    NDPI_BITMASK_SET_ALL(all)就是把映射中所有的應用都進行設置。

 

l  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

l  processing_thread:裏面就一個核心runPcapLoop(thread_id)。

l  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代表的是不停地抓直到抓包出錯的時候停止。

l  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協議檢測的數據源

l  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;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章