/* * Copyright (c) 2008-2024 OARC, Inc. * Copyright (c) 2007-2008, Internet Systems Consortium, Inc. * Copyright (c) 2003-2007, The Measurement Factory, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "dns_message.h" #include "xmalloc.h" #include "syslog_debug.h" #include "tld_list.h" #include "dns_protocol.h" #include "null_index.h" #include "qtype_index.h" #include "qclass_index.h" #include "country_index.h" #include "asn_index.h" #include "tld_index.h" #include "rcode_index.h" #include "client_index.h" #include "client_subnet_index.h" #include "server_ip_addr_index.h" #include "qnamelen_index.h" #include "label_count_index.h" #include "edns_cookie_index.h" #include "edns_nsid_index.h" #include "edns_ede_index.h" #include "edns_ecs_index.h" #include "qname_index.h" #include "msglen_index.h" #include "certain_qnames_index.h" #include "idn_qname_index.h" #include "query_classification_index.h" #include "edns_version_index.h" #include "edns_bufsiz_index.h" #include "do_bit_index.h" #include "rd_bit_index.h" #include "tc_bit_index.h" #include "qr_aa_bits_index.h" #include "opcode_index.h" #include "transport_index.h" #include "dns_ip_version_index.h" #include "dns_source_port_index.h" #include "response_time_index.h" #include "encryption_index.h" #include "ip_direction_index.h" #include "ip_proto_index.h" #include "ip_version_index.h" #include #include #include #include extern int debug_flag; static md_array_list* Arrays = 0; static filter_list* DNSFilters = 0; static indexer indexers[] = { { "client", 0, client_indexer, client_iterator, client_reset }, { "server", 0, sip_indexer, sip_iterator, sip_reset }, { "country", country_init, country_indexer, country_iterator, country_reset }, { "asn", asn_init, asn_indexer, asn_iterator, asn_reset }, { "client_subnet", client_subnet_init, client_subnet_indexer, client_subnet_iterator, client_subnet_reset }, { "null", 0, null_indexer, null_iterator }, { "qclass", 0, qclass_indexer, qclass_iterator, qclass_reset }, { "qnamelen", 0, qnamelen_indexer, qnamelen_iterator, qnamelen_reset }, { "label_count", 0, label_count_indexer, label_count_iterator, label_count_reset }, { "qname", 0, qname_indexer, qname_iterator, qname_reset }, { "second_ld", 0, second_ld_indexer, second_ld_iterator, second_ld_reset }, { "third_ld", 0, third_ld_indexer, third_ld_iterator, third_ld_reset }, { "msglen", 0, msglen_indexer, msglen_iterator, msglen_reset }, { "qtype", 0, qtype_indexer, qtype_iterator, qtype_reset }, { "rcode", 0, rcode_indexer, rcode_iterator, rcode_reset }, { "tld", 0, tld_indexer, tld_iterator, tld_reset }, { "certain_qnames", 0, certain_qnames_indexer, certain_qnames_iterator }, { "query_classification", 0, query_classification_indexer, query_classification_iterator }, { "idn_qname", 0, idn_qname_indexer, idn_qname_iterator }, { "edns_version", indexer_want_edns, edns_version_indexer, edns_version_iterator }, { "edns_bufsiz", indexer_want_edns, edns_bufsiz_indexer, edns_bufsiz_iterator }, { "edns_cookie", indexer_want_edns_options, edns_cookie_indexer, edns_cookie_iterator }, { "edns_cookie_len", indexer_want_edns_options, edns_cookie_len_indexer, edns_cookie_len_iterator, edns_cookie_len_reset }, { "edns_cookie_client", indexer_want_edns_options, edns_cookie_client_indexer, edns_cookie_client_iterator, edns_cookie_client_reset }, { "edns_cookie_server", indexer_want_edns_options, edns_cookie_server_indexer, edns_cookie_server_iterator, edns_cookie_server_reset }, { "edns_ecs", indexer_want_edns_options, edns_ecs_indexer, edns_ecs_iterator }, { "edns_ecs_family", indexer_want_edns_options, edns_ecs_family_indexer, edns_ecs_family_iterator, edns_ecs_family_reset }, { "edns_ecs_source_prefix", indexer_want_edns_options, edns_ecs_source_prefix_indexer, edns_ecs_source_prefix_iterator, edns_ecs_source_prefix_reset }, { "edns_ecs_scope_prefix", indexer_want_edns_options, edns_ecs_scope_prefix_indexer, edns_ecs_scope_prefix_iterator, edns_ecs_scope_prefix_reset }, { "edns_ecs_address", indexer_want_edns_options, edns_ecs_address_indexer, edns_ecs_address_iterator, edns_ecs_address_reset }, { "edns_ecs_subnet", indexer_want_edns_options, edns_ecs_subnet_indexer, edns_ecs_subnet_iterator, edns_ecs_subnet_reset }, { "edns_ede", indexer_want_edns_options, edns_ede_indexer, edns_ede_iterator }, { "edns_ede_code", indexer_want_edns_options, edns_ede_code_indexer, edns_ede_code_iterator, edns_ede_code_reset }, { "edns_ede_textlen", indexer_want_edns_options, edns_ede_textlen_indexer, edns_ede_textlen_iterator, edns_ede_textlen_reset }, { "edns_ede_text", indexer_want_edns_options, edns_ede_text_indexer, edns_ede_text_iterator, edns_ede_text_reset }, { "edns_nsid", indexer_want_edns_options, edns_nsid_indexer, edns_nsid_iterator }, { "edns_nsid_len", indexer_want_edns_options, edns_nsid_len_indexer, edns_nsid_len_iterator, edns_nsid_len_reset }, { "edns_nsid_data", indexer_want_edns_options, edns_nsid_data_indexer, edns_nsid_data_iterator, edns_nsid_data_reset }, { "edns_nsid_text", indexer_want_edns_options, edns_nsid_text_indexer, edns_nsid_text_iterator, edns_nsid_text_reset }, { "do_bit", 0, do_bit_indexer, do_bit_iterator }, { "rd_bit", 0, rd_bit_indexer, rd_bit_iterator }, { "tc_bit", 0, tc_bit_indexer, tc_bit_iterator }, { "opcode", 0, opcode_indexer, opcode_iterator, opcode_reset }, { "transport", 0, transport_indexer, transport_iterator }, { "dns_ip_version", 0, dns_ip_version_indexer, dns_ip_version_iterator, dns_ip_version_reset }, { "dns_source_port", 0, dns_source_port_indexer, dns_source_port_iterator, dns_source_port_reset }, { "dns_sport_range", 0, dns_sport_range_indexer, dns_sport_range_iterator, dns_sport_range_reset }, { "qr_aa_bits", 0, qr_aa_bits_indexer, qr_aa_bits_iterator }, { "response_time", 0, response_time_indexer, response_time_iterator, response_time_reset, response_time_flush }, { "ip_direction", 0, ip_direction_indexer, ip_direction_iterator }, { "ip_proto", 0, ip_proto_indexer, ip_proto_iterator, ip_proto_reset }, { "ip_version", 0, ip_version_indexer, ip_version_iterator, ip_version_reset }, { "encryption", 0, encryption_indexer, encryption_iterator }, { 0 } }; /* * Filters */ static int queries_only_filter(const dns_message* m, const void* ctx) { return m->qr ? 0 : 1; } static int nxdomains_only_filter(const dns_message* m, const void* ctx) { return m->rcode == 3; } static int ad_filter(const dns_message* m, const void* ctx) { return m->ad; } static int popular_qtypes_filter(const dns_message* m, const void* ctx) { switch (m->qtype) { case 1: case 2: case 5: case 6: case 12: case 15: case 28: case 33: case 38: case 255: return 1; default: break; } return 0; } static int aaaa_or_a6_filter(const dns_message* m, const void* ctx) { switch (m->qtype) { case T_AAAA: case T_A6: return 1; default: break; } return 0; } static int idn_qname_filter(const dns_message* m, const void* ctx) { return !strncmp(m->qname, "xn--", 4); } static int root_servers_net_filter(const dns_message* m, const void* ctx) { return !strcmp(m->qname + 1, ".root-servers.net"); } static int chaos_class_filter(const dns_message* m, const void* ctx) { return m->qclass == C_CHAOS; } static int priming_query_filter(const dns_message* m, const void* ctx) { if (m->qtype != T_NS) return 0; if (!strcmp(m->qname, ".")) return 0; return 1; } static int replies_only_filter(const dns_message* m, const void* ctx) { return m->qr ? 1 : 0; } static int qname_filter(const dns_message* m, const void* ctx) { return !regexec((const regex_t*)ctx, m->qname, 0, 0, 0); } static int servfail_filter(const dns_message* m, const void* ctx) { return m->rcode == 2; } static int edns0_filter(const dns_message* m, const void* ctx) { return m->edns.found && m->edns.version == 0; } static int edns0_cookie_filter(const dns_message* m, const void* ctx) { return m->edns.option.cookie; } static int edns0_nsid_filter(const dns_message* m, const void* ctx) { return m->edns.option.nsid; } static int edns0_ede_filter(const dns_message* m, const void* ctx) { return m->edns.option.ede; } static int edns0_ecs_filter(const dns_message* m, const void* ctx) { return m->edns.option.ecs; } /* * Helpers */ static const char* printable_dnsname(const char* name) { static char buf[MAX_QNAME_SZ]; int i; for (i = 0; i < sizeof(buf) - 1;) { if (!*name) break; if (isgraph(*name)) { buf[i] = *name; i++; } else { if (i + 3 > MAX_QNAME_SZ - 1) break; /* expanded character would overflow buffer */ snprintf(buf + i, sizeof(buf) - i - 1, "%%%02x", (unsigned char)*name); i += 3; } name++; } buf[i] = '\0'; return buf; } static void dns_message_print(dns_message* m) { char buf[128]; inXaddr_ntop(m->qr ? &m->tm->dst_ip_addr : &m->tm->src_ip_addr, buf, 128); fprintf(stderr, "%15s:%5d", buf, m->qr ? m->tm->dst_port : m->tm->src_port); fprintf(stderr, "\t%s", (m->tm->proto == IPPROTO_UDP) ? "UDP" : (m->tm->proto == IPPROTO_TCP) ? "TCP" : "???"); fprintf(stderr, "\tQT=%d", m->qtype); fprintf(stderr, "\tQC=%d", m->qclass); fprintf(stderr, "\tlen=%d", m->msglen); fprintf(stderr, "\tqname=%s", printable_dnsname(m->qname)); fprintf(stderr, "\ttld=%s", printable_dnsname(dns_message_tld(m))); fprintf(stderr, "\topcode=%d", m->opcode); fprintf(stderr, "\trcode=%d", m->rcode); fprintf(stderr, "\tmalformed=%d", m->malformed); fprintf(stderr, "\tqr=%d", m->qr); fprintf(stderr, "\trd=%d", m->rd); fprintf(stderr, "\n"); } static indexer* dns_message_find_indexer(const char* in) { indexer* indexer; for (indexer = indexers; indexer->name; indexer++) { if (0 == strcmp(in, indexer->name)) return indexer; } dsyslogf(LOG_ERR, "unknown indexer '%s'", in); return NULL; } static int dns_message_find_filters(const char* fn, filter_list** fl) { char* tok = 0; char* t; char* copy = xstrdup(fn); filter_list* f; if (NULL == copy) return 0; for (t = strtok_r(copy, ",", &tok); t; t = strtok_r(NULL, ",", &tok)) { if (0 == strcmp(t, "any")) continue; for (f = DNSFilters; f; f = f->next) { if (0 == strcmp(t, f->filter->name)) break; } if (f) { fl = md_array_filter_list_append(fl, f->filter); continue; } dsyslogf(LOG_ERR, "unknown filter '%s'", t); xfree(copy); return 0; } xfree(copy); return 1; } /* * Public */ void dns_message_handle(dns_message* m) { md_array_list* a; if (debug_flag > 1) dns_message_print(m); for (a = Arrays; a; a = a->next) md_array_count(a->theArray, m); } int dns_message_add_array(const char* name, const char* fn, const char* fi, const char* sn, const char* si, const char* f, dataset_opt opts) { filter_list* filters = NULL; indexer * indexer1, *indexer2; md_array_list* a; if (NULL == (indexer1 = dns_message_find_indexer(fi))) return 0; if (NULL == (indexer2 = dns_message_find_indexer(si))) return 0; if (0 == dns_message_find_filters(f, &filters)) return 0; a = xcalloc(1, sizeof(*a)); if (a == NULL) { dsyslogf(LOG_ERR, "Cant allocate memory for '%s' DNS message array", name); return 0; } a->theArray = md_array_create(name, filters, fn, indexer1, sn, indexer2); if (NULL == a->theArray) { dsyslogf(LOG_ERR, "Cant allocate memory for '%s' DNS message array", name); xfree(a); return 0; } a->theArray->opts = opts; assert(a->theArray); a->next = Arrays; Arrays = a; return 1; } void dns_message_flush_arrays(void) { md_array_list* a; for (a = Arrays; a; a = a->next) { if (a->theArray->d1.indexer->flush_fn || a->theArray->d2.indexer->flush_fn) md_array_flush(a->theArray); } } void dns_message_report(FILE* fp, md_array_printer* printer) { md_array_list* a; for (a = Arrays; a; a = a->next) { md_array_print(a->theArray, printer, fp); } } void dns_message_clear_arrays(void) { md_array_list* a; for (a = Arrays; a; a = a->next) md_array_clear(a->theArray); } /* * QnameToNld * * qname is a 0-terminated string containing a DNS name * nld is the domain level to find * * return value is a pointer into the qname string. * * Handles the following cases: * qname is empty ("") * qname ends with one or more dots * qname begins with one or more dots * multiple consequtive dots in qname * * TESTS * assert(0 == strcmp(QnameToNld("a.b.c.d", 1), "d")); * assert(0 == strcmp(QnameToNld("a.b.c.d", 2), "c.d")); * assert(0 == strcmp(QnameToNld("a.b.c.d.", 2), "c.d.")); * assert(0 == strcmp(QnameToNld("a.b.c.d....", 2), "c.d....")); * assert(0 == strcmp(QnameToNld("c.d", 5), "c.d")); * assert(0 == strcmp(QnameToNld(".c.d", 5), "c.d")); * assert(0 == strcmp(QnameToNld(".......c.d", 5), "c.d")); * assert(0 == strcmp(QnameToNld("", 1), "")); * assert(0 == strcmp(QnameToNld(".", 1), ".")); * assert(0 == strcmp(QnameToNld("a.b..c..d", 2), "c..d")); * assert(0 == strcmp(QnameToNld("a.b................c..d", 3), "b................c..d")); */ const char* dns_message_QnameToNld(const char* qname, int nld) { const char* e = qname + strlen(qname) - 1; const char* t; int dotcount = 0; int state = 0; /* 0 = not in dots, 1 = in dots */ while (*e == '.' && e > qname) e--; t = e; if (0 == strcmp(t, ".arpa")) dotcount--; if (have_tld_list) { // Use TLD list to find labels that are the "TLD" const char *lt = 0, *ot = t; int done = 0; while (t > qname) { t--; if ('.' == *t) { if (0 == state) { int r = tld_list_find(t + 1); if (r & 1) { // this is a tld lt = t; } if (!r || !(r & 2)) { // no more children if (lt) { // reset to what we last found t = lt; dotcount++; state = 1; } else { // or reset t = ot; state = 0; } done = 1; break; } } state = 1; } else { state = 0; } } if (!done) { // nothing found, reset t t = e; } } while (t > qname && dotcount < nld) { t--; if ('.' == *t) { if (0 == state) dotcount++; state = 1; } else { state = 0; } } while (*t == '.' && t < e) t++; return t; } const char* dns_message_tld(dns_message* m) { if (NULL == m->tld) m->tld = dns_message_QnameToNld(m->qname, 1); return m->tld; } void dns_message_filters_init(void) { filter_list** fl = &DNSFilters; fl = md_array_filter_list_append(fl, md_array_create_filter("queries-only", queries_only_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("replies-only", replies_only_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("nxdomains-only", nxdomains_only_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("popular-qtypes", popular_qtypes_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("idn-only", idn_qname_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("aaaa-or-a6-only", aaaa_or_a6_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("root-servers-net-only", root_servers_net_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("chaos-class", chaos_class_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("priming-query", priming_query_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("servfail-only", servfail_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("edns0-only", edns0_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("edns0-cookie-only", edns0_cookie_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("edns0-nsid-only", edns0_nsid_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("edns0-ede-only", edns0_ede_filter, 0)); fl = md_array_filter_list_append(fl, md_array_create_filter("edns0-ecs-only", edns0_ecs_filter, 0)); (void)md_array_filter_list_append(fl, md_array_create_filter("authentic-data-only", ad_filter, 0)); } void dns_message_indexers_init(void) { indexer* indexer; for (indexer = indexers; indexer->name; indexer++) { if (indexer->init_fn) indexer->init_fn(); } } int add_qname_filter(const char* name, const char* pat) { filter_list** fl = &DNSFilters; regex_t* r; int x; while ((*fl)) fl = &((*fl)->next); r = xcalloc(1, sizeof(*r)); if (NULL == r) { dsyslogf(LOG_ERR, "Cant allocate memory for '%s' qname filter", name); return 0; } if (0 != (x = regcomp(r, pat, REG_EXTENDED | REG_ICASE))) { char errbuf[512]; regerror(x, r, errbuf, 512); dsyslogf(LOG_ERR, "regcomp: %s", errbuf); } (void)md_array_filter_list_append(fl, md_array_create_filter(name, qname_filter, r)); return 1; } void indexer_want_edns(void) { dns_protocol_parse_edns = 1; } void indexer_want_edns_options(void) { dns_protocol_parse_edns = 1; dns_protocol_parse_edns_options = 1; }