最近需要用到nDPI流量监测工具,由于其example
中的ndpiReader
已经十分强大,所以打算在ndpiReader的基础上增加可识别的自定义协议。
在ndpiReader中增加自定义协议的方式有两种:
example/protos.txt
文件来使ndpiReader
匹配相应的协议,但是这种的匹配条件有限,只能通过端口、host和IP地址来定义新协议。修改方法简单,但在需要根据协议格式匹配新协议的时候不适用。对于方法一,网上已经有很多阐述,本文中仅会做简单说明(详细请百度:ndpi 自定义协议)。
而对于方法二,好像目前并没有太多人来解释如何增加并注册自定义协议,网络上现有的文章以及Ntop官方给出的资料似乎是针对nDPI过去的版本进行说明。官方文档及网上大部分说法中的许多函数在现有版本中已经做了变动。
经过本人的摸索,希望将在nDPI中自定义协议的方式记录并分享给大家。
protos.txt
在example
文件中,文件内容如下:
# Format:
# :,:,.....@
tcp:81,tcp:8181@HTTP
udp:5061-5062@SIP
tcp:860,udp:860,tcp:3260,udp:3260@iSCSI
tcp:3000@ntop
# Subprotocols
# Format:
# host:"",host:"",.....@
host:"googlesyndication.com"@Google
host:"venere.com"@Venere
host:"kataweb.it",host:"repubblica.it"@Repubblica
host:"ntop"@ntop
# IP based Subprotocols
# Format:
# ip:,ip:,.....@
ip:213.75.170.11@CustomProtocol
至于怎么根据tcp和udp端口、host和IP地址来添加自定义协议,这个文件里已经给足了例子,大家可以依葫芦画瓢,笔者在此不详细赘述。
以下说明在nDPI较新版本中的源码中添加自定义协议的详细步骤。
为了方便说明,假设我们需要自定义的协议名字叫做ONEONEPROTOCOL。顾名思义,当应用层协议中的数据全为1
,那么就认为这是ONEONEPROTOCOL协议。
首先,每一个协议都必须在头文件ndpi_protocol_ids.h
中有一个对应的协议ID的定义,例如我们可以找到CSGO的定义:
NDPI_PROTOCOL_CSGO = 235, /* Counter-Strike Global Offensive, Dota = 2 */
所以我们也要为ONEONEPROTOCOL注册一个协议ID:
格式为NDPI_PROTOCOL_+协议名 = protocolID,
注意此处的协议ID不可与已有的重复。
NDPI_PROTOCOL_ONEONEPROTOCOL = 243, /* all of the tcp payload is 1*/
在定义完协议ID后,我们必须在src/lib/protocols/
文件夹中创建自定义协议的源文件:oneoneprotocol.c
。该文件用来识别协议格式。
在定义完源文件后,我们需要在源文件中编写内容。首先我们需要导入必要的头文件以及做好相关定义:
#include "ndpi_protocol_ids.h"
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_ONEONEPROTOCOL
#include "ndpi_api.h"
在完成上一步后,我们需要在自定义源文件中实现oneoneprotocol的检测函数,按照nDPI的规范,这个函数需要定义为void ndpi_search_+自定义协议名(struct dpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow)
其中ndpi_struct
为ndpiReader的检测模块结构体,flow
为指向连接状态机的流指针(不懂这些是啥也没关系)。在这个函数体中,我们需要实现判断一个payload是否为oneoneprotocol协议:
void ndpi_search_oneoneprotocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) {
struct ndpi_packet_struct *packet = &flow->packet;
u_int len = 0;
u_int count = 0;
NDPI_LOG_DBG(ndpi_struct, "search oneoneprotocol\n");
while(len < packet->payload_packet_len) {
if (ntohs(*((u_int16_t*)&packet->payload[len])) != '1'){
count++;
}
len++;
}
if(count == packet->payload_packet_len - 1){
NDPI_LOG_INFO(ndpi_struct, "found oneoneprotocol\n");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ONEONEPROTOCOL, NDPI_PROTOCOL_UNKNOWN);
return;
}
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}
写得可能有点乱,但大致的逻辑大家应该看得懂。。。
首先我们将flow
中的flow->packet
取出并存放在packet
变量中,然后输出调试信息:NDPI_LOG_DBG(ndpi_struct, "search oneoneprotocol\n");
。在接下来的语句中就是很简单的判断是否每一个字节都是1了。其中packet->payload_packet_len
表示当前检测的payload的长度,packet->payload
为当前payload的首地址。
注意,如果我们发现当前协议为oneoneprotocol的话,我们使用函数ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ONEONEPROTOCOL, NDPI_PROTOCOL_UNKNOWN)
来将ndpi_struct
中默认的NDPI_PROTOCOL_UNKNOWN
标识为NDPI_PROTOCOL_ONEONEPROTOCOL
,否则我们使用NDPI_EXCLUDE_PROTO(ndpi_struct, flow)
语句来表示判断不成立。
在写完ndpi_search_oneoneprotocol
函数后,在oneoneprotocol.c
文件中还需包含另外一个函数:void init_+自定义协议名+_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask)
用来绑定oneoneprotocol的协议掩码相关信息:
void init_oneoneprotocol_dissector(struct ndpi_detection_module_struct *ndpi_struct,
u_int32_t *id,
NDPI_PROTOCOL_BITMASK *detection_bitmask) {
ndpi_set_bitmask_protocol_detection("oneoneprotocol", ndpi_struct, detection_bitmask, *id,
NDPI_PROTOCOL_ONEONEPROTOCOL,
ndpi_search_oneoneprotocol,
NDPI_SELECTION_BITMASK_PROTOCOL_TCP_WITH_PAYLOAD,
SAVE_DETECTION_BITMASK_AS_UNKNOWN,
ADD_TO_DETECTION_BITMASK);
*id += 1;
}
可以看到,这个函数主要就是调用了ndpi_set_bitmask_protocol_detection
函数,而在这个detection函数中,我们需要重点关注一下几点:
NDPI_PROTOCOL_ONEONEPROTOCOL
为在ndpi_protocol_ids.h
定义的宏关于这个协议的掩码,所有的掩码都在中有着很方便地定义在src/include/ndpi_define.h
中,以下为其中一小段:
/* v4 or v6 */
#define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP (NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP)
#define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP (NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP)
#define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP (NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP)
#define NDPI_SELECTION_BITMASK_PROTOCOL_TCP_WITH_PAYLOAD (NDPI_SELECTION_BITMASK_PROTOCOL_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD)
#define NDPI_SELECTION_BITMASK_PROTOCOL_V6_TCP_WITH_PAYLOAD (NDPI_SELECTION_BITMASK_PROTOCOL_V6_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD)
#define NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD (NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD)
其中v4和v6代表ipv4和ipv6协议,大家可以根据自定义协议的信息来确定这个协议掩码。例如本例oneoneprotocol在ipv4协议上,利用tcp的payload传输,所以选择NDPI_SELECTION_BITMASK_PROTOCOL_TCP_WITH_PAYLOAD
。如果大家不想找已有定义的话,也可以根据以下的宏定义用|
自由组合:
define NDPI_SELECTION_BITMASK_PROTOCOL_IP (1<<0)
#define NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP (1<<1)
#define NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP (1<<2)
#define NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP (1<<3)
#define NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD (1<<4)
#define NDPI_SELECTION_BITMASK_PROTOCOL_NO_TCP_RETRANSMISSION (1<<5)
#define NDPI_SELECTION_BITMASK_PROTOCOL_IPV6 (1<<6)
#define NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6 (1<<7)
#define NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC (1<<8)
在实现完协议源文件后,我们只需在src/lib/ndpi_main.c
中添加我们的函数。
在1934行左右添加ndpi_set_proto_defaults
函数,你们一定看得到上面那一排的其他协议的定义方式,我们同样也只需依葫芦画瓢:
ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ONEONEPROTOCOL,
no_master,
no_master, "ONEONEPROTOCOL", NDPI_PROTOCOL_CATEGORY_CHAT,
ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
以下为几个参数的修改方法:
第一个参数使用给定的默认值ndpi_mod
第二个参数NDPI_PROTOCOL_ACCEPTABLE
,这个宏定义在src/include/ndpi_typedefs.h
中,需要选择相应的宏定义来表示自定义协议的安全性。
typedef enum {
NDPI_PROTOCOL_SAFE = 0, /* Surely doesn't provide risks for the network. (e.g., a news site) */
NDPI_PROTOCOL_ACCEPTABLE, /* Probably doesn't provide risks, but could be malicious (e.g., Dropbox) */
NDPI_PROTOCOL_FUN, /* Pure fun protocol, which may be prohibited by the user policy (e.g., Netflix) */
NDPI_PROTOCOL_UNSAFE, /* Probably provides risks, but could be a normal traffic. Unencrypted protocols with clear pass should be here (e.g., telnet) */
NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, /* Surely is dangerous (ex. Tor). Be prepared to troubles */
NDPI_PROTOCOL_TRACKER_ADS, /* Trackers, Advertisements... */
NDPI_PROTOCOL_UNRATED /* No idea, not implemented or impossible to classify */
} ndpi_protocol_breed_t;
第三个参数需要改成我们在ndpi_protocol_ids.h
定义的宏:NDPI_PROTOCOL_ONEONEPROTOCOL
第四个参数使用给定的默认值no_master
第五个参数使用给定的默认值no_master
第六个参数为我们的协议名"ONEONEPROTOCOL"
第七个参数NDPI_PROTOCOL_CATEGORY_CHAT
,这个宏定义在src/include/ndpi_typedefs.h
中,需要选择相应的宏定义来表示自定义协议的细分类型。
typedef enum {
NDPI_PROTOCOL_CATEGORY_UNSPECIFIED = 0, /* For general services and unknown protocols */
NDPI_PROTOCOL_CATEGORY_MEDIA, /* Multimedia and streaming */
NDPI_PROTOCOL_CATEGORY_VPN, /* Virtual Private Networks */
NDPI_PROTOCOL_CATEGORY_MAIL, /* Protocols to send/receive/sync emails */
NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, /* AFS/NFS and similar protocols */
NDPI_PROTOCOL_CATEGORY_WEB, /* Web/mobile protocols and services */
NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, /* Social networks */
NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, /* Download, FTP, file transfer/sharing */
NDPI_PROTOCOL_CATEGORY_GAME, /* Online games */
NDPI_PROTOCOL_CATEGORY_CHAT, /* Instant messaging */
NDPI_PROTOCOL_CATEGORY_VOIP, /* Real-time communications and conferencing */
NDPI_PROTOCOL_CATEGORY_DATABASE, /* Protocols for database communication */
NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, /* Remote access and control */
NDPI_PROTOCOL_CATEGORY_CLOUD, /* Online cloud services */
NDPI_PROTOCOL_CATEGORY_NETWORK, /* Network infrastructure protocols */
NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, /* Software for collaborative development, including Webmail */
NDPI_PROTOCOL_CATEGORY_RPC, /* High level network communication protocols */
NDPI_PROTOCOL_CATEGORY_STREAMING, /* Streaming protocols */
NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, /* System/Operating System level applications */
NDPI_PROTOCOL_CATEGORY_SW_UPDATE, /* Software update */
/* See #define NUM_CUSTOM_CATEGORIES */
NDPI_PROTOCOL_CATEGORY_CUSTOM_1, /* User custom category 1 */
NDPI_PROTOCOL_CATEGORY_CUSTOM_2, /* User custom category 2 */
NDPI_PROTOCOL_CATEGORY_CUSTOM_3, /* User custom category 3 */
NDPI_PROTOCOL_CATEGORY_CUSTOM_4, /* User custom category 4 */
NDPI_PROTOCOL_CATEGORY_CUSTOM_5, /* User custom category 5 */
/* Payload Content */
NDPI_CONTENT_CATEGORY_AVI,
NDPI_CONTENT_CATEGORY_FLASH,
NDPI_CONTENT_CATEGORY_OGG,
NDPI_CONTENT_CATEGORY_MPEG,
NDPI_CONTENT_CATEGORY_QUICKTIME,
NDPI_CONTENT_CATEGORY_REALMEDIA,
NDPI_CONTENT_CATEGORY_WINDOWSMEDIA,
NDPI_CONTENT_CATEGORY_WEBM,
/* Some custom categories */
CUSTOM_CATEGORY_MINING = 99,
CUSTOM_CATEGORY_MALWARE = 100,
CUSTOM_CATEGORY_ADVERTISEMENT = 101,
CUSTOM_CATEGORY_BANNED_SITE = 102,
CUSTOM_CATEGORY_SITE_UNAVAILABLE = 103,
NDPI_PROTOCOL_NUM_CATEGORIES /*
NOTE: Keep this as last member
Unused as value but useful to getting the number of elements
in this datastructure
*/
} ndpi_protocol_category_t;
第八个参数ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */
可以定义tcp默认端口,例如ndpi_build_default_ports(ports_a, 8080, 0, 0, 0, 0) /* TCP */
代表自定义协议的默认端口为8080端口,而0代表没有默认端口。
第九个参数同上。
在ndpi_main.c
中为自定义协议添加了ndpi_set_proto_defaults
函数后,我们只需在ndpi_set_protocol_detection_bitmask2
函数中添加我们的函数:init_oneoneprotocol_dissector(ndpi_struct, &a, detection_bitmask);
就大功告成了~