From 46130148afe5827a11dba75ee825ffbaed42519d Mon Sep 17 00:00:00 2001 From: RajaMuhammadAwais <1.19938988e+08+RajaMuhammadAwais@users.noreply.github.com> Date: Sun, 23 Aug 2026 11:38:15 +0000 Subject: [PATCH] dns: support an opt-in custom detection port Keep standard DNS, mDNS, and LLMNR port handling unchanged while allowing deployments with DNS on one explicitly configured port. The option is disabled by default and is covered by the official DNS-on-port-80 regression capture, including the expected classification output. --- doc/configuration_parameters.rst | 2 ++ src/include/ndpi_private.h | 1 + src/lib/ndpi_config.c | 1 + src/lib/protocols/dns.c | 25 ++++++++++++------ tests/cfgs/dns_custom_port/config.txt | 1 + .../pcap/dns_on_port_80.pcapng | Bin 0 -> 344 bytes .../result/dns_on_port_80.pcapng.out | 8 ++++++ 7 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 tests/cfgs/dns_custom_port/config.txt create mode 100644 tests/cfgs/dns_custom_port/pcap/dns_on_port_80.pcapng create mode 100644 tests/cfgs/dns_custom_port/result/dns_on_port_80.pcapng.out diff --git a/doc/configuration_parameters.rst b/doc/configuration_parameters.rst index f0105e50f26..59f79c2f96a 100644 --- a/doc/configuration_parameters.rst +++ b/doc/configuration_parameters.rst @@ -186,6 +186,8 @@ List of the supported configuration options: | "dns" | "max_packets_extra_dissection" | 5 | 0 | 255 | After a flow has been classified has DNS, nDPI might analyse more packets to look for a sub-classification or for metadata. This parameter set the upper limit | | | | | | | on the number of these packets. Useful if interested in handling big DNS messages (i.e. AXFR transfers) | +--------------+---------------------------------------------------------------+-----------------+------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| "dns" | "custom_port" | 0 | 0 | 65535 | Enable DNS detection on one additional user-selected TCP or UDP port. The default value 0 disables this option. Standard DNS, mDNS, and LLMNR port behavior is unchanged. | ++--------------+---------------------------------------------------------------+-----------------+------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | "http" | "process_response" | enable | NULL | NULL | Enable/disable processing of HTTP responses. By default, HTTP flows are usually fully classified after the first request/response pair. If this parameter is | | | | | | | disabled, the flows are fully classified after the first request (or after the first response, if the request is missing); in that case, some flow risks are not | | | | | | | checked and some metadata are not exported | diff --git a/src/include/ndpi_private.h b/src/include/ndpi_private.h index 20208ab575a..63b1e9d42c3 100644 --- a/src/include/ndpi_private.h +++ b/src/include/ndpi_private.h @@ -344,6 +344,7 @@ struct ndpi_detection_module_config_struct { int dns_subclassification_enabled; int dns_parse_response_enabled; int dns_max_packets_extra_dissection; + int dns_custom_port; int http_parse_response_enabled; int http_subclassification_enabled; diff --git a/src/lib/ndpi_config.c b/src/lib/ndpi_config.c index 286824a9cee..0410f1da501 100644 --- a/src/lib/ndpi_config.c +++ b/src/lib/ndpi_config.c @@ -177,6 +177,7 @@ const struct cfg_param cfg_params[] = { { "dns", "subclassification", "disable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(dns_subclassification_enabled), NULL }, { "dns", "process_response", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(dns_parse_response_enabled), NULL }, { "dns", "max_packets_extra_dissection", "5", "0", "255", CFG_PARAM_INT, __OFF(dns_max_packets_extra_dissection), NULL }, + { "dns", "custom_port", "0", "0", "65535", CFG_PARAM_INT, __OFF(dns_custom_port), NULL }, { "http", "process_response", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(http_parse_response_enabled), NULL }, { "http", "subclassification", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(http_subclassification_enabled), NULL }, diff --git a/src/lib/protocols/dns.c b/src/lib/protocols/dns.c index 38be95b7fd4..7e01ed7a8e0 100644 --- a/src/lib/protocols/dns.c +++ b/src/lib/protocols/dns.c @@ -136,13 +136,19 @@ static int isLLMNRMulticastAddress(struct ndpi_packet_struct *const packet) /* *********************************************** */ -static u_int16_t checkDNSSubprotocol(u_int16_t sport, u_int16_t dport) { +static u_int16_t checkDNSSubprotocol(struct ndpi_detection_module_struct *ndpi_struct, + u_int16_t sport, u_int16_t dport) { u_int16_t rc = checkPort(sport); if(rc == 0) - return(checkPort(dport)); - else - return(rc); + rc = checkPort(dport); + + if(rc == 0 && ndpi_struct->cfg.dns_custom_port != 0 && + (sport == (u_int16_t)ndpi_struct->cfg.dns_custom_port || + dport == (u_int16_t)ndpi_struct->cfg.dns_custom_port)) + return NDPI_PROTOCOL_DNS; + + return rc; } /* *********************************************** */ @@ -665,7 +671,7 @@ static int is_valid_dns(struct ndpi_detection_module_struct *ndpi_struct, } } else { if(((dns_header->num_queries > 0 && dns_header->num_queries <= NDPI_MAX_DNS_REQUESTS) || /* Don't assume that num_queries must be zero */ - (checkDNSSubprotocol(ntohs(flow->c_port), ntohs(flow->s_port)) == NDPI_PROTOCOL_MDNS && dns_header->num_queries == 0)) && + (checkDNSSubprotocol(ndpi_struct, ntohs(flow->c_port), ntohs(flow->s_port)) == NDPI_PROTOCOL_MDNS && dns_header->num_queries == 0)) && ((dns_header->num_answers > 0 && dns_header->num_answers <= NDPI_MAX_DNS_REQUESTS) || (dns_header->authority_rrs > 0 && dns_header->authority_rrs <= NDPI_MAX_DNS_REQUESTS) || (dns_header->additional_rrs > 0 && dns_header->additional_rrs <= NDPI_MAX_DNS_REQUESTS) || @@ -943,7 +949,7 @@ static int process_hostname(struct ndpi_detection_module_struct *ndpi_struct, char _hostname[256]; u_int8_t hostname_is_valid; - proto->master_protocol = checkDNSSubprotocol(ntohs(flow->c_port), ntohs(flow->s_port)); + proto->master_protocol = checkDNSSubprotocol(ndpi_struct, ntohs(flow->c_port), ntohs(flow->s_port)); proto->app_protocol = flow->detected_protocol_stack[1] != NDPI_PROTOCOL_UNKNOWN ? flow->detected_protocol_stack[0] : NDPI_PROTOCOL_UNKNOWN; /* We try to get hostname only from "standard" query/answer */ @@ -1195,10 +1201,13 @@ void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct nd d_port = ntohs(packet->tcp->dest); } - /* We are able to detect DNS/MDNS/LLMNR only on standard ports (see #1788) */ + /* DNS on nonstandard ports is opt-in to avoid false positives. */ if(!(s_port == DNS_PORT || d_port == DNS_PORT || s_port == MDNS_PORT || d_port == MDNS_PORT || - d_port == LLMNR_PORT)) { + d_port == LLMNR_PORT || + (ndpi_struct->cfg.dns_custom_port != 0 && + (s_port == (u_int16_t)ndpi_struct->cfg.dns_custom_port || + d_port == (u_int16_t)ndpi_struct->cfg.dns_custom_port)))) { NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); return; } diff --git a/tests/cfgs/dns_custom_port/config.txt b/tests/cfgs/dns_custom_port/config.txt new file mode 100644 index 00000000000..a2c8572b01a --- /dev/null +++ b/tests/cfgs/dns_custom_port/config.txt @@ -0,0 +1 @@ +--cfg=dns,custom_port,80 diff --git a/tests/cfgs/dns_custom_port/pcap/dns_on_port_80.pcapng b/tests/cfgs/dns_custom_port/pcap/dns_on_port_80.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..e42199960e288a3975338b647486069d2735cb10 GIT binary patch literal 344 zcmd<$<>d-sU|{gI(UxKa(*L1=kwKdwI597?B(o|tMIotDA*3iVIW@c}F)uwQwMe1N zK+jCiLLsR%GbcsC(!>&|lYs$b4#*4zDERVH!Igo*hJnF>!9g(9`M`=*96&76APuxcW4&AH0iZn~ ztAK#Hyu6$(FSR78IE6XCC>