【智能路由器】ndpi深度报文分析源码框架

【智能路由器】系列文章连接
http://blog.csdn.net/u012819339/article/category/5803489


某些需求可能会要求路由能精确的分析出流经路由的流量是属于什么类型,比如qq,facebook,支付宝、京东……
正好,有这么一个基于opendpi框架的深度报文分析的工具——ndpi

ndpi是在opendpi的基础上编写而来的协议分析工具。源代码编译后生成两个部分,一个是内核层的xt_ndpi.ko模块,用来实时分析流量,另一个是应用层的lib库,给ndpiReader这个工具提供库,用来分析抓包工具提供的文件或者底层网卡提供的数据包。

开发者必须为其想要分析的app的流量 对应开发一个 协议分析器,ndpi已经提供了不少现成的协议分析器,如http,QQ,twitter,vmware,yahoo,mysql,pplive等等。

本篇博客中作者arvik只叙述ndpi源码中形成 内核层的xt_ndpi.ko模块 的源码部分。
之后可能会写一篇介绍ndpi中已有的QQ协议分析器是怎么分析出OICQ协议以识别流量类型 和一篇实战型依葫芦画瓢编写 微信协议分析器的博客。

ndpi的分析过程:

当底层一帧数据被送入ndpi钩子的时候,流经结构大致如下:
1. 打包该数据帧,搜集l3、l4层报头信息
2. 查询链接跟踪(如果已被标识,则直接获取到该数据帧所属协议类型)
3. 从链接跟踪中未获取流量所属协议类型,则进入深度报文分析过程
4. 率先进行协议猜测,调用相应的协议分析器分析
5. 猜测协议错误、此时ndpi会分类遍历相关类型的协议分析器进行分析,直至分析出结果或遍历完所有相关的协议分析器
6. 将分析出的协议类型标记到链接跟踪中,以便下次可直接从连接跟踪中拿到协议类型

先来看内核模块代码:

几个重要结构

ndpi_detection_module_struct:各种协议分析器都将自己的信息都保存在该结构中

struct ndpi_detection_module_struct {

  NDPI_PROTOCOL_BITMASK detection_bitmask; //等价于 struct xxx { uint32_t fds_bits[8]}; struct xxx dection; 其中fds_bits[8]每一位可代表一种协议,最多可代表256中协议
  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

  /* 各种类型回调函数数组,每个协议都会对应一个回调函数 */
  struct ndpi_call_function_struct callback_buffer[NDPI_MAX_SUPPORTED_PROTOCOLS + 1];
  u_int32_t callback_buffer_size;

  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;

#ifdef NDPI_ENABLE_DEBUG_MESSAGES
  /* 调试信息相关回调 */
  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;

  /* 注册子协议处理函数  数组 */
  struct ndpi_subprotocol_conf_struct subprotocol_conf[NDPI_MAX_SUPPORTED_PROTOCOLS + 1];

  u_int ndpi_num_supported_protocols;
  u_int ndpi_num_custom_protocols;

  /* 用于HTTP/DNS/HTTPS host 匹配 */
  ndpi_automa host_automa,                     /* Used for DNS/HTTPS */
    content_automa,                            /* Used for HTTP subprotocol_detection */
    subprotocol_automa,                        /* Used for HTTP subprotocol_detection */
    bigrams_automa, impossible_bigrams_automa; /* TOR */

  /* IP-based protocol detection */
  void *protocols_ptree;

 //部分协议的私有参数
  /* irc parameters */
  u_int32_t irc_timeout;
  /* gnutella parameters */
  u_int32_t gnutella_timeout;
  /* battlefield parameters */
  u_int32_t battlefield_timeout;
  /* thunder parameters */
  u_int32_t thunder_timeout;
  /* SoulSeek parameters */
  u_int32_t soulseek_connection_ip_tick_timeout;
  /* rtsp parameters */
  u_int32_t rtsp_connection_timeout;
  /* tvants parameters */
  u_int32_t tvants_connection_timeout;
  /* rstp */
  u_int32_t orb_rstp_ts_timeout;
  /* yahoo */
  u_int8_t yahoo_detect_http_connections;
  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_t proto_defaults[NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS];

//分别是http、dns和网络方向探测允许标识位
  u_int8_t http_dont_dissect_response:1, dns_dissect_response:1, 
    direction_detect_disable:1; /* disable internal detection of packet direction */
};

ndpi_flow_struct:携带一系列数据包组成的数据流信息,协议分析已经完成的部分信息都会保存在流中,当然也会保存部分协议分析器分析的私有数据信息

struct ndpi_flow_struct {
  u_int16_t detected_protocol_stack[NDPI_PROTOCOL_HISTORY_SIZE];
#ifndef WIN32
  __attribute__ ((__packed__))
#endif
  u_int16_t protocol_stack_info;

  /* init parameter, internal used to set up timestamp,... */
  u_int16_t guessed_protocol_id, guessed_host_protocol_id;

  u_int8_t protocol_id_already_guessed:1, host_already_guessed:1, init_finished:1, setup_packet_direction:1, packet_direction:1;

  /*
     if ndpi_struct->direction_detect_disable == 1
     tcp sequence number connection tracking
  */
  u_int32_t next_tcp_seq_nr[2];

  /*
     the tcp / udp / other l4 value union
     used to reduce the number of bytes for tcp or udp protocol states
  */
  union {
    struct ndpi_flow_tcp_struct tcp;
    struct ndpi_flow_udp_struct udp;
  } l4;

  /*
     Pointer to src or dst
     that identifies the
     server of this connection
  */
  struct ndpi_id_struct *server_id;
  /* HTTP host or DNS query */
  u_char host_server_name[192];
  /* Via HTTP User-Agent */
  u_char detected_os[32];
  /* Via HTTP X-Forwarded-For */
  u_char nat_ip[24];
  /* Bittorrent hash */
  u_char bittorent_hash[20];

  /*
     This structure below will not not stay inside the protos
     structure below as HTTP is used by many subprotocols
     such as FaceBook, Google... so it is hard to know
     when to use it or not. Thus we leave it outside for the
     time being.
  */
  struct {
    ndpi_http_method method;
    char *url, *content_type;
  } http;

  union {

    /* the only fields useful for nDPI and ntopng */
    struct {
      u_int8_t num_queries, num_answers, reply_code;
      u_int16_t query_type, query_class, rsp_type;
    } dns;

    struct {
      u_int8_t request_code;
      u_int8_t version;
    } ntp;

    struct {
      char client_certificate[48], server_certificate[48];
    } ssl;
  } protos;

  /*** ALL protocol specific 64 bit variables here ***/

  /* protocols which have marked a connection as this connection cannot be protocol XXX, multiple u_int64_t */
  NDPI_PROTOCOL_BITMASK excluded_protocol_bitmask;

  u_int8_t num_stun_udp_pkts;

#ifdef NDPI_PROTOCOL_REDIS
  u_int8_t redis_s2d_first_char, redis_d2s_first_char;
#endif
  u_int16_t packet_counter;           // can be 0 - 65000
  u_int16_t packet_direction_counter[2];
  u_int16_t byte_counter[2];
#ifdef NDPI_PROTOCOL_BITTORRENT
  u_int8_t bittorrent_stage;              // can be 0 - 255
#endif
#ifdef NDPI_PROTOCOL_DIRECTCONNECT
  u_int32_t directconnect_stage:2;        // 0 - 1
#endif
#ifdef NDPI_PROTOCOL_SIP
#ifdef NDPI_PROTOCOL_YAHOO
  u_int32_t sip_yahoo_voice:1;
#endif
#endif
#ifdef NDPI_PROTOCOL_HTTP
  u_int32_t http_detected:1;
#endif
#ifdef NDPI_PROTOCOL_RTSP
  u_int32_t rtsprdt_stage:2;
  u_int32_t rtsp_control_flow:1;
#endif
#ifdef NDPI_PROTOCOL_YAHOO
  u_int32_t yahoo_detection_finished:2;
#endif
#ifdef NDPI_PROTOCOL_ZATTOO
  u_int32_t zattoo_stage:3;
#endif
#ifdef NDPI_PROTOCOL_QQ
  u_int32_t qq_stage:3;
#endif
#ifdef NDPI_PROTOCOL_THUNDER
  u_int32_t thunder_stage:2;                // 0 - 3
#endif
#ifdef NDPI_PROTOCOL_OSCAR
  u_int32_t oscar_ssl_voice_stage:3;
  u_int32_t oscar_video_voice:1;
#endif
#ifdef NDPI_PROTOCOL_FLORENSIA
  u_int32_t florensia_stage:1;
#endif
#ifdef NDPI_PROTOCOL_SOCKS
  u_int32_t socks5_stage:2;                 // 0 - 3
  u_int32_t socks4_stage:2;                 // 0 - 3
#endif
#ifdef NDPI_PROTOCOL_EDONKEY
  u_int32_t edonkey_stage:2;                    // 0 - 3
#endif
#ifdef NDPI_PROTOCOL_FTP_CONTROL
  u_int32_t ftp_control_stage:2;
#endif
#ifdef NDPI_PROTOCOL_RTMP
  u_int32_t rtmp_stage:2;
#endif
#ifdef NDPI_PROTOCOL_PANDO
  u_int32_t pando_stage:3;
#endif
#ifdef NDPI_PROTOCOL_STEAM
  u_int32_t steam_stage:3;
  u_int32_t steam_stage1:3;         // 0 - 4
  u_int32_t steam_stage2:2;         // 0 - 2
  u_int32_t steam_stage3:2;         // 0 - 2
#endif
#ifdef NDPI_PROTOCOL_PPLIVE
  u_int32_t pplive_stage1:3;            // 0 - 6
  u_int32_t pplive_stage2:2;            // 0 - 2
  u_int32_t pplive_stage3:2;            // 0 - 2
#endif
#ifdef NDPI_PROTOCOL_STARCRAFT
  u_int32_t starcraft_udp_stage : 3;    // 0-7
#endif

  /* internal structures to save functions calls */
  struct ndpi_packet_struct packet;
  struct ndpi_flow_struct *flow;
  struct ndpi_id_struct *src;
  struct ndpi_id_struct *dst;
};

初始化

ndpi的初始化在ndpi_init_detection_module()函数中,该函数申请好一个ndpi_detection_module_struct结构的空间后,初始化了一些协议的私有参数后就交给ndpi_init_protocol_defaults了。

struct ndpi_detection_module_struct *ndpi_init_detection_module() {
  struct ndpi_detection_module_struct *ndpi_str = ndpi_malloc(sizeof(struct ndpi_detection_module_struct));

... //次要代码省略

  ndpi_str->thunder_timeout = NDPI_THUNDER_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;
  ndpi_str->yahoo_detect_http_connections = NDPI_YAHOO_DETECT_HTTP_CONNECTIONS;

... //次要代码省略

  ndpi_init_protocol_defaults(ndpi_str);
  return ndpi_str;
}

接下来看看ndpi_init_protocol_defaults(ndpi_str),该函数为各种协议初始化协议默认的tcp及udp端口,调用ndpi_set_proto_defaults完成设置,最终调用addDefaultPort来向二叉树结构中添加协议端口设置信息。

static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndpi_mod) {

  int i;
  ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS];
  u_int16_t no_master[2] = { NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO },
    custom_master[2];

    /* Reset all settings */
    memset(ndpi_mod->proto_defaults, 0, sizeof(ndpi_mod->proto_defaults));

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_UNRATED, NDPI_PROTOCOL_UNKNOWN,
                no_master,
                no_master, "Unknown",
                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_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_FTP_CONTROL,
                no_master,
                no_master, "FTP_CONTROL",
                ndpi_build_default_ports(ports_a, 21, 0, 0, 0, 0) /* TCP */,
                ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
    ndpi_set_proto_defaults(ndpi_mod,NDPI_PROTOCOL_ACCEPTABLE,NDPI_PROTOCOL_RX,
                no_master,
                no_master, "RX",
                ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0),  /* TCP */
                ports_b);  /* UDP */

... //部分重复代码省略

    /* calling function for host and content matched protocols */
    init_string_based_protocols(ndpi_mod);

    for(i=0; i<(int)ndpi_mod->ndpi_num_supported_protocols; i++) {
      if(ndpi_mod->proto_defaults[i].protoName == NULL) {
    printf("[NDPI] %s(missing protoId=%d) INTERNAL ERROR: not all protocols have been initialized\n", __FUNCTION__, i);
      }
    }
}               

ndpi_init_detection_module()函数之后,执行了以下两句:

    NDPI_BITMASK_RESET(protocols_bitmask); //重置所有协议标志位,即:禁止所有协议探测器
    ndpi_set_protocol_detection_bitmask2(ndpi_struct, &protocols_bitmask);

重点看看ndpi_set_protocol_detection_bitmask2函数,

void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *ndpi_struct,
                      const NDPI_PROTOCOL_BITMASK * dbm)
{
  NDPI_PROTOCOL_BITMASK detection_bitmask_local;
  NDPI_PROTOCOL_BITMASK *detection_bitmask = &detection_bitmask_local;
  u_int32_t a = 0;

  NDPI_BITMASK_SET(detection_bitmask_local, *dbm);
  NDPI_BITMASK_SET(ndpi_struct->detection_bitmask, *dbm);

  /* set this here to zero to be interrupt safe */
  ndpi_struct->callback_buffer_size = 0;

  /* HTTP */
  init_http_dissector(ndpi_struct, &a, detection_bitmask);

  /* Stracraft */
  init_starcraft_dissector(ndpi_struct, &a, detection_bitmask);

  /* SSL */
  init_ssl_dissector(ndpi_struct, &a, detection_bitmask);

    ... // 接下来的都是调用不同协议分析器的初始化函数,这些初始化函数都是统一的这种格式,后续arvik讲解怎么在opendpi的基础框架上编写自己的私有协议分析器的时候再谈

    ...//代码至此,所有协议分析器的初始化函数都调用了个遍,接下来就是根据各种协议初始化函数中注册的相关信息对各个协议进行分类编组,比如某个协议分析器是基于tcp报文还是udp报文,是带负载还是不带负载...

  /* ----------------------------------------------------------------- */


  ndpi_struct->callback_buffer_size = a;

  NDPI_LOG(NDPI_PROTOCOL_UNKNOWN, ndpi_struct, NDPI_LOG_DEBUG,
       "callback_buffer_size is %u\n", ndpi_struct->callback_buffer_size);

  /* now build the specific buffer for tcp, udp and non_tcp_udp */
  ndpi_struct->callback_buffer_size_tcp_payload = 0;
  ndpi_struct->callback_buffer_size_tcp_no_payload = 0;
  for(a = 0; a < ndpi_struct->callback_buffer_size; a++) {
    if((ndpi_struct->callback_buffer[a].ndpi_selection_bitmask
    & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP |
       NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP |
       NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC)) != 0) {
      NDPI_LOG(NDPI_PROTOCOL_UNKNOWN, ndpi_struct, NDPI_LOG_DEBUG,
           "callback_buffer_tcp_payload, adding buffer %u as entry %u\n", a,
           ndpi_struct->callback_buffer_size_tcp_payload);

      memcpy(&ndpi_struct->callback_buffer_tcp_payload[ndpi_struct->callback_buffer_size_tcp_payload],
         &ndpi_struct->callback_buffer[a], sizeof(struct ndpi_call_function_struct));
      ndpi_struct->callback_buffer_size_tcp_payload++;

      if((ndpi_struct->
      callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == 0) {
    NDPI_LOG(NDPI_PROTOCOL_UNKNOWN, ndpi_struct, NDPI_LOG_DEBUG,
         "\tcallback_buffer_tcp_no_payload, additional adding buffer %u to no_payload process\n", a);

    memcpy(&ndpi_struct->callback_buffer_tcp_no_payload
           [ndpi_struct->callback_buffer_size_tcp_no_payload], &ndpi_struct->callback_buffer[a],
           sizeof(struct ndpi_call_function_struct));
    ndpi_struct->callback_buffer_size_tcp_no_payload++;
      }
    }
  }

  ndpi_struct->callback_buffer_size_udp = 0;
  for(a = 0; a < ndpi_struct->callback_buffer_size; a++) {
    if((ndpi_struct->callback_buffer[a].ndpi_selection_bitmask & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP |
                                  NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP |
                                  NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC))
       != 0) {
      NDPI_LOG(NDPI_PROTOCOL_UNKNOWN, ndpi_struct, NDPI_LOG_DEBUG,
           "callback_buffer_size_udp: adding buffer : %u as entry %u\n", a, ndpi_struct->callback_buffer_size_udp);

      memcpy(&ndpi_struct->callback_buffer_udp[ndpi_struct->callback_buffer_size_udp],
         &ndpi_struct->callback_buffer[a], sizeof(struct ndpi_call_function_struct));
      ndpi_struct->callback_buffer_size_udp++;
    }
  }

  ndpi_struct->callback_buffer_size_non_tcp_udp = 0;
  for(a = 0; a < ndpi_struct->callback_buffer_size; a++) {
    if((ndpi_struct->callback_buffer[a].ndpi_selection_bitmask & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP |
                                  NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP |
                                  NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP))
       == 0
       || (ndpi_struct->
       callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC) != 0) {
      NDPI_LOG(NDPI_PROTOCOL_UNKNOWN, ndpi_struct, NDPI_LOG_DEBUG,
           "callback_buffer_non_tcp_udp: adding buffer : %u as entry %u\n", a, ndpi_struct->callback_buffer_size_non_tcp_udp);

      memcpy(&ndpi_struct->callback_buffer_non_tcp_udp[ndpi_struct->callback_buffer_size_non_tcp_udp],
         &ndpi_struct->callback_buffer[a], sizeof(struct ndpi_call_function_struct));
      ndpi_struct->callback_buffer_size_non_tcp_udp++;
    }
  }
}

之后,ndpi为 ndpi_id_structndpi_flow_struct申请对应的缓存页后初始化就算完成了。

使能相关分析器

当模块被iptables加载的时候,会根据传入的参数来激活相关协议分析器。主要调用ndpi_enable_protocols ()进行激活

ndpi_mt_check(const struct xt_mtchk_param *par)
{
    const struct xt_ndpi_mtinfo *info = par->matchinfo;

        if (NDPI_BITMASK_IS_ZERO(info->flags)) {
                pr_info("None selected protocol.\n");
                return -EINVAL;
        }

    NDPI_BITMASK_RESET(protocols_bitmask);
        ndpi_enable_protocols (info);

    return nf_ct_l3proto_try_module_get (par->family);
}

数据包分析

直接从dnpi_mt这个钩子看起,首先skb_copy一份skb用于分析,然后获取连接跟踪信息,当前时间信息,最后交由ndpi_process_packet处理,看看该函数里的部分代码片:

  1. 根据该帧packet的连接跟踪信息,拿到其所属的流结构,接着又进行一系列的条件检查及协议查找,找到则直接返回对应的协议。
flow = ndpi_flow_search (&osdpi_flow_root, ct);
    if (iph->protocol == IPPROTO_TCP) {
                if (tcph->syn) {
            spin_unlock_bh (&flow_lock);
            return proto;
        }
                else if ((tcph->fin || tcph->rst) && flow != NULL) {
            ndpi_kill_flow(ct, ipsrc, ipdst);
            spin_unlock_bh (&flow_lock);
            return proto;
        }
    }
    else if (iph->protocol == IPPROTO_ICMP) {
        spin_unlock_bh (&flow_lock);
        return NDPI_PROTOCOL_IP_ICMP;
    }
    else {
        if (nf_ct_is_dying(ct)) {
            ndpi_kill_flow(ct, ipsrc, ipdst);
            spin_unlock_bh (&flow_lock);
            return proto;
        }
    }
  1. 上一步未能查找到对应的流,则申请一个新的flow结构并设置初始参数,然后根据源ip和目的ip查找id,未找到则释放当前的流结构。
        spin_lock_bh (&flow_lock);
        src = ndpi_id_search (&osdpi_id_root, ipsrc);
    if (src == NULL) {
                src = ndpi_alloc_id(ipsrc);
                if (src == NULL) {
            kmem_cache_free (osdpi_flow_cache, curflow);
                spin_unlock_bh (&flow_lock);
            return proto;
        }
    }
    else if (!exist_flow) kref_get (&src->refcnt);

        dst = ndpi_id_search (&osdpi_id_root, ipdst);
    if (dst == NULL) {
                dst = ndpi_alloc_id(ipdst);
                if (dst == NULL) {
            kmem_cache_free (osdpi_flow_cache, curflow);
                spin_unlock_bh (&flow_lock);
            return proto;
        }
    }
    else if (!exist_flow) kref_get (&dst->refcnt);
  1. 接下来,实际的协议探测开始了,一切都在ndpi_detection_process_packet中…
ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct *ndpi_struct,
                        struct ndpi_flow_struct *flow,
                        const unsigned char *packet,
                        const unsigned short packetlen,
                        const u_int64_t current_tick_l,
                        struct ndpi_id_struct *src,
                        struct ndpi_id_struct *dst)
{

...//一些条件检查代码,省略

ndpi_connection_tracking(ndpi_struct, flow); //设置该flow的链接跟踪结构信息

//对该packet进行分类
  ndpi_selection_packet = NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC;
  if(flow->packet.iph != NULL)
    ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_IP | NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6;

  if(flow->packet.tcp != NULL)
    ndpi_selection_packet |=
      (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP);

  if(flow->packet.udp != NULL)
    ndpi_selection_packet |=
      (NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP);

  if(flow->packet.payload_packet_len != 0)
    ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD;

  if(flow->packet.tcp_retransmission == 0)
    ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_NO_TCP_RETRANSMISSION;

#ifdef NDPI_DETECTION_SUPPORT_IPV6
  if(flow->packet.iphv6 != NULL)
    ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6;
#endif  

//开始猜测协议id
 if((!flow->protocol_id_already_guessed)
     && (
#ifdef NDPI_DETECTION_SUPPORT_IPV6
     flow->packet.iphv6 ||
#endif
     flow->packet.iph)) {
    u_int16_t sport, dport;
    u_int8_t protocol;
    u_int32_t saddr, daddr;

    flow->protocol_id_already_guessed = 1;

#ifdef NDPI_DETECTION_SUPPORT_IPV6
    if(flow->packet.iphv6 != NULL) {
      protocol = flow->packet.iphv6->ip6_ctlun.ip6_un1.ip6_un1_nxt, saddr = 0, daddr = 0;
    } else
#endif
      {
    protocol = flow->packet.iph->protocol;
      }

    if(flow->packet.udp) sport = ntohs(flow->packet.udp->source), dport = ntohs(flow->packet.udp->dest);
    else if(flow->packet.tcp) sport = ntohs(flow->packet.tcp->source), dport = ntohs(flow->packet.tcp->dest);
    else sport = dport = 0;

    flow->guessed_protocol_id = (int16_t)ndpi_guess_protocol_id(ndpi_struct, protocol, sport, dport);

    if(flow->packet.iph) {
      if((flow->guessed_host_protocol_id = ndpi_network_ptree_match(ndpi_struct, (struct in_addr *)&flow->packet.iph->saddr)) == NDPI_PROTOCOL_UNKNOWN)
    flow->guessed_host_protocol_id = ndpi_network_ptree_match(ndpi_struct, (struct in_addr *)&flow->packet.iph->daddr);
    }

 //检测该packet对应的flow协议分析器
 check_ndpi_flow_func(ndpi_struct, flow, &ndpi_selection_packet);

  a = flow->packet.detected_protocol_stack[0];
  if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(ndpi_struct->detection_bitmask, a) == 0)
    a = NDPI_PROTOCOL_UNKNOWN;

  if(a != NDPI_PROTOCOL_UNKNOWN) {
    int i;

    for(i=0; (i<sizeof(flow->host_server_name)) && (flow->host_server_name[i] != '\0'); i++)
      flow->host_server_name[i] = tolower(flow->host_server_name[i]);

    flow->host_server_name[i] ='\0';
  }
  1. check_ndpi_flow_func分三种情况进行检测,分别基于tcp、udp和其他协议报文下的检测
void check_ndpi_flow_func(struct ndpi_detection_module_struct *ndpi_struct,
              struct ndpi_flow_struct *flow,
              NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) {
  if(flow->packet.tcp != NULL)
    check_ndpi_tcp_flow_func(ndpi_struct, flow, ndpi_selection_packet);
  else if(flow->packet.udp != NULL)
    check_ndpi_udp_flow_func(ndpi_struct, flow, ndpi_selection_packet);
  else
    check_ndpi_other_flow_func(ndpi_struct, flow, ndpi_selection_packet);
}
  1. 抽取check_ndpi_tcp_flow_func看看,该函数前半部分尝试用猜测的协议id来直接调用相应的协议分析器代码,后半部分就是遍历其他注册在tcp协议下,且标志位都符合条件的协议分析器进行分析
void check_ndpi_tcp_flow_func(struct ndpi_detection_module_struct *ndpi_struct,
                  struct ndpi_flow_struct *flow,
                  NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) {
  void *func = NULL;
  u_int32_t a;
  u_int16_t proto_index = ndpi_struct->proto_defaults[flow->guessed_protocol_id].protoIdx;
  int16_t proto_id = ndpi_struct->proto_defaults[flow->guessed_protocol_id].protoId;
  NDPI_PROTOCOL_BITMASK detection_bitmask;

  NDPI_SAVE_AS_BITMASK(detection_bitmask, flow->packet.detected_protocol_stack[0]);

  if(flow->packet.payload_packet_len != 0) {
    if((proto_id != NDPI_PROTOCOL_UNKNOWN)
       && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                   ndpi_struct->callback_buffer[proto_index].excluded_protocol_bitmask) == 0
       && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer[proto_index].detection_bitmask,
                   detection_bitmask) != 0
       && (ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask & *ndpi_selection_packet) == ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask) {
      if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN)
     && (ndpi_struct->proto_defaults[flow->guessed_protocol_id].func != NULL))
    ndpi_struct->proto_defaults[flow->guessed_protocol_id].func(ndpi_struct, flow),
      func = ndpi_struct->proto_defaults[flow->guessed_protocol_id].func;
    }

    if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) {
      for(a = 0; a < ndpi_struct->callback_buffer_size_tcp_payload; a++) {
        if((func != ndpi_struct->callback_buffer_tcp_payload[a].func)
       && (ndpi_struct->callback_buffer_tcp_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) == ndpi_struct->callback_buffer_tcp_payload[a].ndpi_selection_bitmask
       && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                   ndpi_struct->callback_buffer_tcp_payload[a].excluded_protocol_bitmask) == 0
       && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer_tcp_payload[a].detection_bitmask,
                   detection_bitmask) != 0) {
      ndpi_struct->callback_buffer_tcp_payload[a].func(ndpi_struct, flow);


      if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN)
        break; /* Stop after detecting the first protocol */
    }
      }
    }
  } else {
    /* no payload */
    if((proto_id != NDPI_PROTOCOL_UNKNOWN)
       && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                   ndpi_struct->callback_buffer[proto_index].excluded_protocol_bitmask) == 0
       && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer[proto_index].detection_bitmask,
                   detection_bitmask) != 0
       && (ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask
       & *ndpi_selection_packet) == ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask) {
      if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN)
     && (ndpi_struct->proto_defaults[flow->guessed_protocol_id].func != NULL)
     && ((ndpi_struct->callback_buffer[flow->guessed_protocol_id].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == 0))
    ndpi_struct->proto_defaults[flow->guessed_protocol_id].func(ndpi_struct, flow),
      func = ndpi_struct->proto_defaults[flow->guessed_protocol_id].func;
    }

    for(a = 0; a < ndpi_struct->callback_buffer_size_tcp_no_payload; a++) {
      if((func != ndpi_struct->callback_buffer_tcp_payload[a].func)
     && (ndpi_struct->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) ==
     ndpi_struct->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask
     && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                 ndpi_struct->
                 callback_buffer_tcp_no_payload[a].excluded_protocol_bitmask) == 0
     && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer_tcp_no_payload[a].detection_bitmask,
                 detection_bitmask) != 0) {
    ndpi_struct->callback_buffer_tcp_no_payload[a].func(ndpi_struct, flow);

    if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN)
      break; /* Stop after detecting the first protocol */
      }
    }
  }
}

使用

ndpi的使用也很简单,arvik写一个例子:(限制流经路由的qq报文)

iptables -t mangle -A PREROUTING -m ndpi --qq -j DROP

好啦,作者arvik,ndpi深度报文分析后续内容见博客专栏 【智能路由器】系列文章连接
http://blog.csdn.net/u012819339/article/category/5803489

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值