简单的说,首先抓包,再从数据链路层开始解析,一直解析到传输层是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;