簡單的說,首先抓包,再從數據鏈路層開始解析,一直解析到傳輸層是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;