nDPI的DNS协议解析

nDPI的DNS协议解析

1. 说明

把nDpi的DNS协议解析部分拿出来看看,写了一些注释。使用的是nDpi的1.6版本,该版本下的文件结构比较简单,协议解析的代码都在 src/lib/protocols目录下。dns的解析代码自然就是dns.c了。nDpi的协议解析代码基本有着相同的结构,协议解析的入口函数一般定义为 ndpi_search_###。比如DNS的入口函数就是ndpi_search_dns()。

2. 工具函数

这个文件下还有几个简单的函数:
// 一个递归的函数来计算dns中域名字段占据报文的长度
static u_int getNameLength
//用来计算ip地址的,
static char* ndpi_intoa_v4
// 用报文中得到一个16字节的整数
static u_int16_t get16

3. 源代码注释

//  dns 的入口函数
void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow)
{
  struct ndpi_packet_struct *packet = &flow->packet;  // 定义包接口
  u_int16_t dport = 0, sport = 0;

#define NDPI_MAX_DNS_REQUESTS           16

  NDPI_LOG(NDPI_PROTOCOL_DNS, ndpi_struct, NDPI_LOG_DEBUG, "search DNS.\n");  // 打日志


  // 根据使用的是tcp还是udp,获取端口号
  if (packet->udp != NULL) {
    sport = ntohs(packet->udp->source),  dport = ntohs(packet->udp->dest);
    NDPI_LOG(NDPI_PROTOCOL_DNS, ndpi_struct, NDPI_LOG_DEBUG, "calculated dport over UDP.\n");
  } else  if(packet->tcp != NULL) {
    sport = ntohs(packet->tcp->source), dport = ntohs(packet->tcp->dest);
    NDPI_LOG(NDPI_PROTOCOL_DNS, ndpi_struct, NDPI_LOG_DEBUG, "calculated dport over tcp.\n");
  }
  // 如果端口包含53 可以确定为dns协议。否则退出整个函数。并且有数据内容。这个if一直到函数的结尾了
  if(((dport == 53) || (sport == 53) || (dport == 5355))
     && (packet->payload_packet_len > sizeof(struct dns_packet_header))) {



    int i = packet->tcp ? 2 : 0;  // 一般dns协议使用的是udp协议。DNS服务器之间才会使用tcp。如果是tcp协议的话,需要偏移两个字节,??为什么


    struct dns_packet_header header, *dns = (struct dns_packet_header*)&packet->payload[i];
    u_int8_t is_query, ret_code, is_dns = 0;
    u_int32_t a_record[NDPI_MAX_DNS_REQUESTS] = { 0 }, query_offset, num_a_records = 0;

    // 获取dns协议的头部,将网络字节序转换为本地字节序
    header.flags = ntohs(dns->flags);
    header.transaction_id = ntohs(dns->transaction_id);
    header.num_queries = ntohs(dns->num_queries);
    header.answer_rrs = ntohs(dns->answer_rrs);
    header.authority_rrs = ntohs(dns->authority_rrs);
    header.additional_rrs = ntohs(dns->additional_rrs);

    is_query = (header.flags & 0x8000) ? 0 : 1; // 从flags的第一位获取dns是查询报文还是响应报文

    ret_code = is_query ? 0 : (header.flags & 0x0F);  // 获取dns的flags字段的rcode,0表示没有差错,3表示名字差错(域名不存在)
    i += sizeof(struct dns_packet_header);   // 偏移值移动dns头部的长度
    query_offset = i; // 设置偏移值

    // 如果是一个查询报文
    if(is_query) {
      /* DNS Request */
      if((header.num_queries > 0) && (header.num_queries <= NDPI_MAX_DNS_REQUESTS)
     && (((header.flags & 0x2800) == 0x2800 /* Dynamic DNS Update */)
         || ((header.answer_rrs == 0) && (header.authority_rrs == 0)))) {
    /* This is a good query */ // dns 动态更新?开来查询报文分两种,一种是一般的dns请求,一种是dns服务器的动态更新。0x2800,opcode是5,不知道是什么


    is_dns = 1;
    if(header.num_queries > 0) { // 循环处理多个查询报文,并没有处理具体的查询内容,只是走了一个统计吗?

      while(i < packet->payload_packet_len) {

           if(packet->payload[i] == '\0') {
        i++;
        flow->protos.dns.query_type = get16(&i, packet->payload); // 获取了查询类型, flow的结构体是在是太强大了,好大。在ndpi_typedefs.h里定义的。
        break;
          } else
        i++;
        }
    }
      }
    }// 查询包处理结束

     else {  // 如果包是一个响应包
      /* DNS Reply */

      flow->server_id = flow->dst;

      if((header.num_queries <= NDPI_MAX_DNS_REQUESTS) /* Don't assume that num_queries must be zero */
     && (((header.answer_rrs > 0) && (header.answer_rrs <= NDPI_MAX_DNS_REQUESTS))
         || ((header.authority_rrs > 0) && (header.authority_rrs <= NDPI_MAX_DNS_REQUESTS))
         || ((header.additional_rrs > 0) && (header.additional_rrs <= NDPI_MAX_DNS_REQUESTS)))
     ) {
    /* This is a good reply */
    is_dns = 1;

    i++;

    // 越过查询问题字段的查询名称
    if(packet->payload[i] != '\0') {
      while((i < packet->payload_packet_len)
        && (packet->payload[i] != '\0')) {
        i++;
      }

      i++;
    }

    i += 4; // 越过查询问题字段的查询类型和查询类

    // 如果有回答报文,处理回答报文字段
    if(header.answer_rrs > 0) {
      u_int16_t rsp_type /*, rsp_class */;
      u_int16_t num;

      for(num = 0; num < header.answer_rrs; num++) { // 处理每一个回答报文
        u_int16_t data_len;

        if((i+6) >= packet->payload_packet_len) {  // 包长度的合理性判断
          break;
        }

        if((data_len = getNameLength(i, packet->payload, packet->payload_packet_len)) == 0) {  // 包合法性检查,看看包的回答字段的域名是否合理
          break;
        } else
          i += data_len;

        rsp_type = get16(&i, packet->payload);  // 得到第num个回答的查询类型
        // rsp_class = get16(&i, packet->payload);

        i += 4; // 这里应该是+8吧,要不下面的就不正确了,没有考虑生存时间字段的4个字节

        data_len = get16(&i, packet->payload);

        if((data_len <= 1) || (data_len > (packet->payload_packet_len-i))) {
          break;
        }

        flow->protos.dns.rsp_type = rsp_type;

        if(rsp_type == 1 /* A */) { // 如果是A类,那么就是一个4字节的ip地址
          if(data_len == 4) {
        u_int32_t v = ntohl(*((u_int32_t*)&packet->payload[i]));

        if(num_a_records < (NDPI_MAX_DNS_REQUESTS-1)) // 做记录
          a_record[num_a_records++] = v;
        else
          break; /* One record is enough */  // 明明是16个记录
          }
        }

        if(data_len == 0) {
          break;
        }

        i += data_len;
      } /* for */
    }
      } // 正确的响应包处理结束

      if((header.num_queries <= NDPI_MAX_DNS_REQUESTS) // 响应包的另外一种正确形式
     && ((header.answer_rrs == 0)
         || (header.authority_rrs == 0)
         || (header.additional_rrs == 0))
     && (ret_code != 0 /* 0 == OK */)
     ) {
    /* This is a good reply */
    is_dns = 1;
      }
    } //响应包处理结束

    if(is_dns) {
      int j = 0;

      flow->protos.dns.num_queries = (u_int8_t)header.num_queries, 
    flow->protos.dns.num_answers = (u_int8_t)(header.answer_rrs+header.authority_rrs+header.additional_rrs),
      flow->protos.dns.ret_code = ret_code;

      i = query_offset+1;

      // 下面是一些数据的统计功能了,全部放到flow里面。
      while((i < packet->payload_packet_len)
        && (j < (sizeof(flow->host_server_name)-1))   
        && (packet->payload[i] != '\0')) {
    flow->host_server_name[j] = tolower(packet->payload[i]);
    if(flow->host_server_name[j] < ' ')
      flow->host_server_name[j] = '.';  
    j++, i++;
      }

      if(a_record[0] != 0) {
    char a_buf[32];
    int i;

    for(i=0; i<num_a_records; i++) {
      j += snprintf((char*)&flow->host_server_name[j], sizeof(flow->host_server_name)-1-j, "%s%s",
            (i == 0) ? "@" : ";",
            ndpi_intoa_v4(a_record[i], a_buf, sizeof(a_buf)));
    }
      }

      flow->host_server_name[j] = '\0';

      if(j > 0) {
#ifdef DEBUG
    printf("==> %s\n", flow->host_server_name);
#endif

    if(ndpi_struct->match_dns_host_names)
      ndpi_match_string_subprotocol(ndpi_struct, flow, 
                    (char *)flow->host_server_name,
                    strlen((const char*)flow->host_server_name));
      }

      i++;

      memcpy(&flow->protos.dns.query_type, &packet->payload[i], 2); 
      flow->protos.dns.query_type  = ntohs(flow->protos.dns.query_type), i += 2;

      memcpy(&flow->protos.dns.query_class, &packet->payload[i], 2); 
      flow->protos.dns.query_class  = ntohs(flow->protos.dns.query_class), i += 2;

#ifdef DEBUG
      printf("%s [type=%04X][class=%04X]\n", flow->host_server_name, flow->protos.dns.query_type, flow->protos.dns.query_class);
#endif

      if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) {
    /* 
       Do not set the protocol with DNS if ndpi_match_string_subprotocol() has
       matched a subprotocol
    */
    NDPI_LOG(NDPI_PROTOCOL_DNS, ndpi_struct, NDPI_LOG_DEBUG, "found DNS.\n");      
    ndpi_int_add_connection(ndpi_struct, flow, (dport == 5355) ? NDPI_PROTOCOL_LLMNR : NDPI_PROTOCOL_DNS, NDPI_REAL_PROTOCOL);
      }
    } else {
      flow->protos.dns.bad_packet = 1;
      NDPI_LOG(NDPI_PROTOCOL_DNS, ndpi_struct, NDPI_LOG_DEBUG, "exclude DNS.\n");
      NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_DNS);
    }
  }
}
  • 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、付费专栏及课程。

余额充值