/* * dns.c * * Copyright (C) 2012-15 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nDPI is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . * */ #include "ndpi_protocols.h" #ifdef NDPI_PROTOCOL_DNS static u_int getNameLength(u_int i, const u_int8_t *payload, u_int payloadLen) { if(payload[i] == 0x00) return(1); else if(payload[i] == 0xC0) return(2); else { u_int8_t len = payload[i]; u_int8_t off = len + 1; if(off == 0) /* Bad packet */ return(0); else return(off + getNameLength(i+off, payload, payloadLen)); } } /* *********************************************** */ static char* ndpi_intoa_v4(unsigned int addr, char* buf, u_short bufLen) { char *cp, *retStr; uint byte; int n; cp = &buf[bufLen]; *--cp = '\0'; n = 4; do { byte = addr & 0xff; *--cp = byte % 10 + '0'; byte /= 10; if(byte > 0) { *--cp = byte % 10 + '0'; byte /= 10; if(byte > 0) *--cp = byte + '0'; } *--cp = '.'; addr >>= 8; } while (--n > 0); /* Convert the string to lowercase */ retStr = (char*)(cp+1); return(retStr); } /* *********************************************** */ static u_int16_t get16(int *i, const u_int8_t *payload) { u_int16_t v = *(u_int16_t*)&payload[*i]; (*i) += 2; return(ntohs(v)); } /* *********************************************** */ struct dns_packet_header { u_int16_t transaction_id, flags, num_queries, answer_rrs, authority_rrs, additional_rrs; } __attribute__((packed)); 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"); 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"); } if(((dport == 53) || (sport == 53) || (dport == 5355)) && (packet->payload_packet_len > sizeof(struct dns_packet_header))) { int i = packet->tcp ? 2 : 0; 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; 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; ret_code = is_query ? 0 : (header.flags & 0x0F); i += sizeof(struct dns_packet_header); 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 */ 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); 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; 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); // Skip past the CLASS (2 octets) and TTL (4 octets) fields. i += 6; 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 */) { 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 */ } } 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; 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; ihost_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_set_detected_protocol(ndpi_struct, flow, (dport == 5355) ? NDPI_PROTOCOL_LLMNR : NDPI_PROTOCOL_DNS, NDPI_PROTOCOL_UNKNOWN); } } 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); } } } #endif