nDPI注册自定义协议解析

前言

​ 最近需要用到nDPI流量监测工具,由于其example中的ndpiReader已经十分强大,所以打算在ndpiReader的基础上增加可识别的自定义协议。

在ndpiReader中增加自定义协议的方式有两种:

  1. 是通过编辑example/protos.txt文件来使ndpiReader匹配相应的协议,但是这种的匹配条件有限,只能通过端口、host和IP地址来定义新协议。修改方法简单,但在需要根据协议格式匹配新协议的时候不适用。
  2. 通过在源码中注册自定义协议以及自己编写自定义协议的匹配逻辑,来自定义协议。这种方法相对于方法一来说相对复杂(其实也并不算复杂),但是协议自定义匹配方式更为灵活。

对于方法一,网上已经有很多阐述,本文中仅会做简单说明(详细请百度:ndpi 自定义协议)。

而对于方法二,好像目前并没有太多人来解释如何增加并注册自定义协议,网络上现有的文章以及Ntop官方给出的资料似乎是针对nDPI过去的版本进行说明。官方文档及网上大部分说法中的许多函数在现有版本中已经做了变动。

经过本人的摸索,希望将在nDPI中自定义协议的方式记录并分享给大家。

方法一:修改protos.txt

protos.txtexample文件中,文件内容如下:

#  Format:
#  <tcp|udp>:<port>,<tcp|udp>:<port>,.....@<proto>

tcp:81,tcp:8181@HTTP
udp:5061-5062@SIP
tcp:860,udp:860,tcp:3260,udp:3260@iSCSI
tcp:3000@ntop

#  Subprotocols
#  Format:
#  host:"<value>",host:"<value>",.....@<subproto>

host:"googlesyndication.com"@Google
host:"venere.com"@Venere
host:"kataweb.it",host:"repubblica.it"@Repubblica
host:"ntop"@ntop
#  IP based Subprotocols
#  Format:
#  ip:<value>,ip:<value>,.....@<subproto>

ip:213.75.170.11@CustomProtocol

至于怎么根据tcp和udp端口、host和IP地址来添加自定义协议,这个文件里已经给足了例子,大家可以依葫芦画瓢,笔者在此不详细赘述。

方法二:添加源码

以下说明在nDPI较新版本中的源码中添加自定义协议的详细步骤。

为了方便说明,假设我们需要自定义的协议名字叫做ONEONEPROTOCOL。顾名思义,当应用层协议中的数据全为1,那么就认为这是ONEONEPROTOCOL协议。

在头文件添加新的协议ID

首先,每一个协议都必须在头文件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"

编写ndpi_search函数

在完成上一步后,我们需要在自定义源文件中实现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)语句来表示判断不成立。

编写dissector函数

在写完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函数中,我们需要重点关注一下几点:

  1. 第一个字符串参数可以传入当前的自定义协议名
  2. 参数第二行的NDPI_PROTOCOL_ONEONEPROTOCOL为在ndpi_protocol_ids.h定义的宏
  3. 参数第三行应该传入刚刚写的检测函数的函数名
  4. 参数第四行为协议的掩码

关于这个协议的掩码,所有的掩码都在中有着很方便地定义在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)

在ndpi_main.c中注册

在实现完协议源文件后,我们只需在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);就大功告成了~

测试

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据你提供的代码片段,可以看出你正在使用 `nDPI` 库对 `DPDK` 抓取的数据包进行协议识别,并将识别结果写入到新的 pcap 文件中。 在代码中,`$READER` 变量指向了一个文件路径,这个路径代表了 `nDPI` 库提供的用于读取和解析数据包的程序。 首先,脚本中的 `build_results` 函数遍历了指定目录中所有以 `.pcap` 扩展名结尾的文件(通过 `PCAPS` 变量保存文件名列表),并使用 `$READER` 执行命令对每个 pcap 文件进行处理。 命令 `$READER -q -i pcap/$f -w result/$f.out` 的含义如下: - `-q` 参数表示以静默模式运行,即不输出额外的信息。 - `-i pcap/$f` 参数指定要读取的 pcap 文件路径。 - `-w result/$f.out` 参数指定要写入的结果文件路径。 这个命令的作用是将输入的 pcap 文件通过 nDPI 库进行解析,并将识别结果写入到 `result/$f.out` 文件中。 接下来,`check_results` 函数对已经生成的结果文件进行检查。它使用相似的逻辑遍历 `PCAPS` 列表中的每个 pcap 文件: - 首先,检查对应的结果文件是否存在。 - 如果结果文件存在,首先执行命令 `$READER -q -i pcap/$f -w /tmp/reader.out`,将输入文件通过 nDPI 库处理,并将结果写入到临时文件 `/tmp/reader.out` 中。 - 接着,使用 `diff` 命令比较结果文件和临时文件的差异,使用 `wc -l` 命令统计差异行数,并将结果存储在 `NUM_DIFF` 变量中。 - 如果差异行数为 0,则打印 `"$f OK"` 表示结果一致。 - 否则,打印 `"$f ERROR"` 表示结果不一致,并打印执行的命令和差异的内容。 - 最后,删除临时文件 `/tmp/reader.out`。 整个脚本的目的是使用 `nDPI` 库对抓取的数据包进行协议识别,生成对应的结果文件,并在检查阶段验证识别结果是否正确。如果识别结果与期望结果不一致,则输出错误信息,并将退出状态码设为 1。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值