From 5b6a62bb37bd9c65ed956785e39415df64bfdab3 Mon Sep 17 00:00:00 2001 From: Ivan Nardi Date: Thu, 13 Feb 2025 14:51:17 +0100 Subject: [PATCH] DNS: rework/isolate code to process domain name --- src/lib/protocols/dns.c | 167 ++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 75 deletions(-) diff --git a/src/lib/protocols/dns.c b/src/lib/protocols/dns.c index d02ce28e91f..0c047553615 100644 --- a/src/lib/protocols/dns.c +++ b/src/lib/protocols/dns.c @@ -648,6 +648,85 @@ static int search_dns_again(struct ndpi_detection_module_struct *ndpi_struct, st /* *********************************************** */ +static int process_hostname(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + ndpi_master_app_protocol *proto) { + struct ndpi_packet_struct *packet = &ndpi_struct->packet; + char *dot; + u_int len, is_mdns, off = sizeof(struct ndpi_dns_packet_header) + (packet->tcp ? 2 : 0); + char _hostname[256]; + u_int8_t hostname_is_valid; + + is_mdns = (proto->master_protocol == NDPI_PROTOCOL_MDNS); + + /* TODO: should we overwrite existing hostname? + For the time being, keep the old/current behavior */ + + hostname_is_valid = ndpi_grab_dns_name(packet, &off, _hostname, sizeof(_hostname), &len, is_mdns); + +#ifdef DNS_DEBUG + printf("[DNS] [%s]\n", _hostname); +#endif + + ndpi_hostname_sni_set(flow, (const u_int8_t *)_hostname, len, is_mdns ? NDPI_HOSTNAME_NORM_LC : NDPI_HOSTNAME_NORM_ALL); + + if (hostname_is_valid == 0) + ndpi_set_risk(ndpi_struct, flow, NDPI_INVALID_CHARACTERS, "Invalid chars detected in domain name"); + + /* Ignore reverse DNS queries */ + if(strstr(_hostname, ".in-addr.") == NULL) { + dot = strchr(_hostname, '.'); + + if(dot) { + uintptr_t first_element_len = dot - _hostname; + + if((first_element_len > 48) && (!is_mdns)) { + /* + The length of the first element in the query is very long + and this might be an issue or indicate an exfiltration + */ + + if(ends_with(ndpi_struct, _hostname, "multi.surbl.org") + || ends_with(ndpi_struct, _hostname, "spamhaus.org") + || ends_with(ndpi_struct, _hostname, "rackcdn.com") + || ends_with(ndpi_struct, _hostname, "akamaiedge.net") + || ends_with(ndpi_struct, _hostname, "mx-verification.google.com") + || ends_with(ndpi_struct, _hostname, "amazonaws.com") + ) + ; /* Check common domain exceptions [TODO: if the list grows too much use a different datastructure] */ + else + ndpi_set_risk(ndpi_struct, flow, NDPI_DNS_SUSPICIOUS_TRAFFIC, "Long DNS host name"); + } + } + } + + if(strlen(flow->host_server_name) > 0) { + ndpi_protocol_match_result ret_match; + + /* Avoid updating classification if subclassification is disabled */ + proto->app_protocol = ndpi_match_host_subprotocol(ndpi_struct, flow, + flow->host_server_name, + strlen(flow->host_server_name), + &ret_match, + proto->master_protocol, + ndpi_struct->cfg.dns_subclassification_enabled ? 1 : 0); + /* Add to FPC DNS cache */ + if(ndpi_struct->cfg.fpc_enabled && + proto->app_protocol != NDPI_PROTOCOL_UNKNOWN && + proto->app_protocol != proto->master_protocol && + (flow->protos.dns.rsp_type == 0x1 || flow->protos.dns.rsp_type == 0x1c) && /* A, AAAA */ + ndpi_struct->fpc_dns_cache) { + ndpi_lru_add_to_cache(ndpi_struct->fpc_dns_cache, + fpc_dns_cache_key_from_dns_info(flow), proto->app_protocol, + ndpi_get_current_time(flow)); + } + + ndpi_check_dga_name(ndpi_struct, flow, flow->host_server_name, 1, 0, proto->app_protocol != NDPI_PROTOCOL_UNKNOWN); + } + + return 0; +} + static void search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &ndpi_struct->packet; int payload_offset = 0; @@ -668,19 +747,17 @@ static void search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct if(packet->payload_packet_len > sizeof(struct ndpi_dns_packet_header)+payload_offset) { struct ndpi_dns_packet_header dns_header; - char *dot; - u_int len, off; + u_int off; int invalid = search_valid_dns(ndpi_struct, flow, &dns_header, payload_offset, &is_query, is_mdns); ndpi_protocol ret; u_int num_queries, idx; - char _hostname[256]; - ret.proto.master_protocol = NDPI_PROTOCOL_UNKNOWN; - ret.proto.app_protocol = (d_port == LLMNR_PORT) ? NDPI_PROTOCOL_LLMNR : (((d_port == MDNS_PORT) && isLLMNRMulticastAddress(packet) ) ? NDPI_PROTOCOL_MDNS : NDPI_PROTOCOL_DNS); + ret.proto.master_protocol = checkDNSSubprotocol(s_port, d_port); + ret.proto.app_protocol = NDPI_PROTOCOL_UNKNOWN; if(invalid) { #ifdef DNS_DEBUG - pritf("[DNS] invalid packet\n"); + printf("[DNS] invalid packet\n"); #endif NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; @@ -741,78 +818,14 @@ static void search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct } } /* for */ - u_int8_t hostname_is_valid = ndpi_grab_dns_name(packet, &off, _hostname, sizeof(_hostname), &len, is_mdns); - - ndpi_hostname_sni_set(flow, (const u_int8_t *)_hostname, len, is_mdns ? NDPI_HOSTNAME_NORM_LC : NDPI_HOSTNAME_NORM_ALL); - - if (hostname_is_valid == 0) - ndpi_set_risk(ndpi_struct, flow, NDPI_INVALID_CHARACTERS, "Invalid chars detected in domain name"); - - /* Ignore reverse DNS queries */ - if(strstr(_hostname, ".in-addr.") == NULL) { - dot = strchr(_hostname, '.'); - - if(dot) { - uintptr_t first_element_len = dot - _hostname; - - if((first_element_len > 48) && (!is_mdns)) { - /* - The lenght of the first element in the query is very long - and this might be an issue or indicate an exfiltration - */ - - if(ends_with(ndpi_struct, _hostname, "multi.surbl.org") - || ends_with(ndpi_struct, _hostname, "spamhaus.org") - || ends_with(ndpi_struct, _hostname, "rackcdn.com") - || ends_with(ndpi_struct, _hostname, "akamaiedge.net") - || ends_with(ndpi_struct, _hostname, "mx-verification.google.com") - || ends_with(ndpi_struct, _hostname, "amazonaws.com") - ) - ; /* Check common domain exceptions [TODO: if the list grows too much use a different datastructure] */ - else - ndpi_set_risk(ndpi_struct, flow, NDPI_DNS_SUSPICIOUS_TRAFFIC, "Long DNS host name"); - } - } - } - - if(strlen(flow->host_server_name) > 0) { - ndpi_protocol_match_result ret_match; - - /* Avoid updating classification if subclassification is disabled */ - ret.proto.app_protocol = ndpi_match_host_subprotocol(ndpi_struct, flow, - flow->host_server_name, - strlen(flow->host_server_name), - &ret_match, - NDPI_PROTOCOL_DNS, - ndpi_struct->cfg.dns_subclassification_enabled ? 1 : 0); - /* Add to FPC DNS cache */ - if(ndpi_struct->cfg.fpc_enabled && - ret.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN && - ret.proto.app_protocol != NDPI_PROTOCOL_DNS && - (flow->protos.dns.rsp_type == 0x1 || flow->protos.dns.rsp_type == 0x1c) && /* A, AAAA */ - ndpi_struct->fpc_dns_cache) { - ndpi_lru_add_to_cache(ndpi_struct->fpc_dns_cache, - fpc_dns_cache_key_from_dns_info(flow), ret.proto.app_protocol, - ndpi_get_current_time(flow)); - } - - ndpi_check_dga_name(ndpi_struct, flow, flow->host_server_name, 1, 0, ret.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN); - - if(!ndpi_struct->cfg.dns_subclassification_enabled) - ret.proto.app_protocol = NDPI_PROTOCOL_UNKNOWN; - - if(ret.proto.app_protocol == NDPI_PROTOCOL_UNKNOWN) - ret.proto.master_protocol = checkDNSSubprotocol(s_port, d_port); - else - ret.proto.master_protocol = NDPI_PROTOCOL_DNS; - - /* Category is always NDPI_PROTOCOL_CATEGORY_NETWORK, regardless of the subprotocol */ - flow->category = NDPI_PROTOCOL_CATEGORY_NETWORK; - } + process_hostname(ndpi_struct, flow, &ret.proto); /* Report if this is a DNS query or reply */ flow->protos.dns.is_query = is_query; + if(!ndpi_struct->cfg.dns_subclassification_enabled) + ret.proto.app_protocol = NDPI_PROTOCOL_UNKNOWN; + if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN || ret.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) { @@ -821,7 +834,8 @@ static void search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct if(is_query) { if(ndpi_struct->cfg.dns_parse_response_enabled) { /* We have never triggered extra-dissection for LLMNR. Keep the old behavior */ - if(ret.proto.master_protocol != NDPI_PROTOCOL_LLMNR) { + if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_LLMNR && + flow->detected_protocol_stack[1] != NDPI_PROTOCOL_LLMNR) { /* Don't use just 1 as in TCP DNS more packets could be returned (e.g. ACK). */ flow->max_extra_packets_to_check = 5; flow->extra_packets_func = search_dns_again; @@ -829,6 +843,9 @@ static void search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct } } } + /* Category is always NDPI_PROTOCOL_CATEGORY_NETWORK, regardless of the subprotocol */ + flow->category = NDPI_PROTOCOL_CATEGORY_NETWORK; + if(is_query) return;