diff --git a/libs/visor_dns/dns.h b/libs/visor_dns/dns.h index 0afd89a6b..22633067c 100644 --- a/libs/visor_dns/dns.h +++ b/libs/visor_dns/dns.h @@ -214,7 +214,6 @@ static std::unordered_map RCodeNames({ {7, "YXRRSET"}, {8, "NXRRSET"}, {9, "NOTAUTH"}, - {9, "NOTAUTH"}, {10, "NOTZONE"}, {11, "DSOTYPENI"}, {16, "BADVERS"}, @@ -228,4 +227,28 @@ static std::unordered_map RCodeNames({ {23, "BADCOOKIE"}, }); +static std::unordered_map RCodeNumbers({ + {"NOERROR", 0}, + {"FORMERR", 1}, + {"SRVFAIL", 2}, + {"NXDOMAIN", 3}, + {"NOTIMP", 4}, + {"REFUSED", 5}, + {"YXDOMAIN", 6}, + {"YXRRSET", 7}, + {"NXRRSET", 8}, + {"NOTAUTH", 9}, + {"NOTZONE", 10}, + {"DSOTYPENI", 11}, + {"BADVERS", 16}, + {"BADSIG", 16}, + {"BADKEY", 17}, + {"BADTIME", 18}, + {"BADMODE", 19}, + {"BADNAME", 20}, + {"BADALG", 21}, + {"BADTRUNC", 22}, + {"BADCOOKIE", 23}, +}); + } \ No newline at end of file diff --git a/src/handlers/dns/v1/DnsStreamHandler.cpp b/src/handlers/dns/v1/DnsStreamHandler.cpp index 8f1bd6e53..55076b67f 100644 --- a/src/handlers/dns/v1/DnsStreamHandler.cpp +++ b/src/handlers/dns/v1/DnsStreamHandler.cpp @@ -59,21 +59,47 @@ void DnsStreamHandler::start() // Setup Filters if (config_exists("exclude_noerror") && config_get("exclude_noerror")) { _f_enabled.set(Filters::ExcludingRCode); - _f_rcode = NoError; + _f_rcodes.push_back(NoError); } else if (config_exists("only_rcode")) { + std::vector rcodes; uint64_t want_code; try { want_code = config_get("only_rcode"); } catch (const std::exception &e) { - throw ConfigException("DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer"); + try { + rcodes = config_get("only_rcode"); + } catch (const std::exception &e) { + throw ConfigException("DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer or an array"); + } } - if (RCodeNames.find(want_code) != RCodeNames.end()) { - _f_enabled.set(Filters::OnlyRCode); - _f_rcode = want_code; + _f_enabled.set(Filters::OnlyRCode); + if (rcodes.empty()) { + if (RCodeNames.find(want_code) != RCodeNames.end()) { + _f_rcodes.push_back(static_cast(want_code)); + } else { + throw ConfigException("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode"); + } + _register_predicate_filter(Filters::OnlyRCode, "only_rcode", std::to_string(want_code)); } else { - throw ConfigException("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode"); + for (const auto &rcode : rcodes) { + if (std::all_of(rcode.begin(), rcode.end(), ::isdigit)) { + auto value = std::stoul(rcode); + if (RCodeNames.find(value) == RCodeNames.end()) { + throw ConfigException(fmt::format("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode: {}", value)); + } + _f_rcodes.push_back(value); + } else { + std::string upper_rcode{rcode}; + std::transform(upper_rcode.begin(), upper_rcode.end(), upper_rcode.begin(), + [](unsigned char c) { return std::toupper(c); }); + if (RCodeNumbers.find(upper_rcode) != RCodeNumbers.end()) { + _f_rcodes.push_back(RCodeNumbers[upper_rcode]); + } else { + throw ConfigException(fmt::format("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode: {}", rcode)); + } + } + } } - _register_predicate_filter(Filters::OnlyRCode, "only_rcode", std::to_string(_f_rcode)); } if (config_exists("only_queries") && config_get("only_queries")) { _f_enabled.set(Filters::OnlyQueries); @@ -430,8 +456,17 @@ inline void DnsStreamHandler::_register_predicate_filter(Filters filter, std::st } inline bool DnsStreamHandler::_filtering(DnsLayer &payload, [[maybe_unused]] PacketDirection dir, [[maybe_unused]] pcpp::ProtocolType l3, [[maybe_unused]] pcpp::ProtocolType l4, [[maybe_unused]] uint16_t port, timespec stamp) { - if (_f_enabled[Filters::ExcludingRCode] && payload.getDnsHeader()->responseCode == _f_rcode) { - goto will_filter; + if (_f_enabled[Filters::ExcludingRCode]) { + auto rcode = payload.getDnsHeader()->responseCode; + if (std::any_of(_f_rcodes.begin(), _f_rcodes.end(), [rcode](uint16_t f_rcode) { return rcode == f_rcode; })) { + goto will_filter; + } + } + if (_f_enabled[Filters::OnlyRCode] && !_using_predicate_signals) { + auto rcode = payload.getDnsHeader()->responseCode; + if (std::none_of(_f_rcodes.begin(), _f_rcodes.end(), [rcode](uint16_t f_rcode) { return rcode == f_rcode; })) { + goto will_filter; + } } if (_f_enabled[Filters::AnswerCount] && payload.getAnswerCount() != _f_answer_count) { goto will_filter; diff --git a/src/handlers/dns/v1/DnsStreamHandler.h b/src/handlers/dns/v1/DnsStreamHandler.h index f537b94a6..2f9839710 100644 --- a/src/handlers/dns/v1/DnsStreamHandler.h +++ b/src/handlers/dns/v1/DnsStreamHandler.h @@ -358,7 +358,7 @@ class DnsStreamHandler final : public visor::StreamMetricsHandler _c_enabled; - uint16_t _f_rcode{0}; + std::vector _f_rcodes; uint64_t _f_answer_count{0}; std::vector _f_qnames; std::vector _f_qtypes; diff --git a/src/handlers/dns/v1/tests/test_dns_layer.cpp b/src/handlers/dns/v1/tests/test_dns_layer.cpp index 44e8e9548..7aaf3cf9c 100644 --- a/src/handlers/dns/v1/tests/test_dns_layer.cpp +++ b/src/handlers/dns/v1/tests/test_dns_layer.cpp @@ -332,7 +332,7 @@ TEST_CASE("DNS Filters: only_rcode nx", "[pcap][net]") REQUIRE(j["wire_packets"]["filtered"] == 0); } -TEST_CASE("DNS Filters: only_rcode refused", "[pcap][dns]") +TEST_CASE("DNS Filters: only_rcode nx and refused", "[pcap][dns]") { PcapInputStream stream{"pcap-test"}; @@ -346,7 +346,7 @@ TEST_CASE("DNS Filters: only_rcode refused", "[pcap][dns]") c.config_set("num_periods", 1); DnsStreamHandler dns_handler{"dns-test", stream_proxy, &c}; - dns_handler.config_set("only_rcode", Refused); + dns_handler.config_set("only_rcode", {"nxdomain", "5"}); dns_handler.start(); stream.start(); @@ -357,11 +357,11 @@ TEST_CASE("DNS Filters: only_rcode refused", "[pcap][dns]") REQUIRE(counters.RNOERROR.value() == 0); REQUIRE(counters.SRVFAIL.value() == 0); REQUIRE(counters.REFUSED.value() == 1); - REQUIRE(counters.NX.value() == 0); + REQUIRE(counters.NX.value() == 1); REQUIRE(counters.NODATA.value() == 0); nlohmann::json j; dns_handler.metrics()->bucket(0)->to_json(j); - REQUIRE(j["wire_packets"]["filtered"] == 0); + REQUIRE(j["wire_packets"]["filtered"] == 22); } TEST_CASE("DNS Filters: only_qtypes AAAA and TXT", "[pcap][dns]") { @@ -829,7 +829,7 @@ TEST_CASE("DNS filter exceptions", "[pcap][dns][filter]") SECTION("only_rcode as string") { dns_handler.config_set("only_rcode", "1"); - REQUIRE_THROWS_WITH(dns_handler.start(), "DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer"); + REQUIRE_THROWS_WITH(dns_handler.start(), "DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer or an array"); } SECTION("only_rcode invalid") diff --git a/src/handlers/dns/v2/DnsStreamHandler.cpp b/src/handlers/dns/v2/DnsStreamHandler.cpp index 5de596ca1..63bf329fd 100644 --- a/src/handlers/dns/v2/DnsStreamHandler.cpp +++ b/src/handlers/dns/v2/DnsStreamHandler.cpp @@ -60,19 +60,45 @@ void DnsStreamHandler::start() // Setup Filters if (config_exists("exclude_noerror") && config_get("exclude_noerror")) { _f_enabled.set(Filters::ExcludingRCode); - _f_rcode = NoError; + _f_rcodes.push_back(NoError); } else if (config_exists("only_rcode")) { + std::vector rcodes; uint64_t want_code; try { want_code = config_get("only_rcode"); } catch (const std::exception &e) { - throw ConfigException("DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer"); + try { + rcodes = config_get("only_rcode"); + } catch (const std::exception &e) { + throw ConfigException("DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer or an array"); + } } - if (RCodeNames.find(want_code) != RCodeNames.end()) { - _f_enabled.set(Filters::OnlyRCode); - _f_rcode = want_code; + _f_enabled.set(Filters::OnlyRCode); + if (rcodes.empty()) { + if (RCodeNames.find(want_code) != RCodeNames.end()) { + _f_rcodes.push_back(static_cast(want_code)); + } else { + throw ConfigException("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode"); + } } else { - throw ConfigException("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode"); + for (const auto &rcode : rcodes) { + if (std::all_of(rcode.begin(), rcode.end(), ::isdigit)) { + auto value = std::stoul(rcode); + if (RCodeNames.find(value) == RCodeNames.end()) { + throw ConfigException(fmt::format("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode: {}", value)); + } + _f_rcodes.push_back(value); + } else { + std::string upper_rcode{rcode}; + std::transform(upper_rcode.begin(), upper_rcode.end(), upper_rcode.begin(), + [](unsigned char c) { return std::toupper(c); }); + if (RCodeNumbers.find(upper_rcode) != RCodeNumbers.end()) { + _f_rcodes.push_back(RCodeNumbers[upper_rcode]); + } else { + throw ConfigException(fmt::format("DnsStreamHandler: only_rcode filter contained an invalid/unsupported rcode: {}", rcode)); + } + } + } } } if (config_exists("only_dnssec_response") && config_get("only_dnssec_response")) { @@ -412,10 +438,17 @@ inline bool DnsStreamHandler::_filtering(DnsLayer &payload, PacketDirection dir, if (_f_enabled[Filters::DisableOut] && dir == PacketDirection::toHost) { goto will_filter; } - if (_f_enabled[Filters::ExcludingRCode] && payload.getDnsHeader()->responseCode == _f_rcode) { - goto will_filter; - } else if (_f_enabled[Filters::OnlyRCode] && payload.getDnsHeader()->responseCode != _f_rcode) { - goto will_filter; + if (_f_enabled[Filters::ExcludingRCode]) { + auto rcode = payload.getDnsHeader()->responseCode; + if (std::any_of(_f_rcodes.begin(), _f_rcodes.end(), [rcode](uint16_t f_rcode) { return rcode == f_rcode; })) { + goto will_filter; + } + } + if (_f_enabled[Filters::OnlyRCode]) { + auto rcode = payload.getDnsHeader()->responseCode; + if (std::none_of(_f_rcodes.begin(), _f_rcodes.end(), [rcode](uint16_t f_rcode) { return rcode == f_rcode; })) { + goto will_filter; + } } if (_f_enabled[Filters::AnswerCount] && payload.getAnswerCount() != _f_answer_count) { goto will_filter; diff --git a/src/handlers/dns/v2/DnsStreamHandler.h b/src/handlers/dns/v2/DnsStreamHandler.h index 94ff8d31a..9b968d30a 100644 --- a/src/handlers/dns/v2/DnsStreamHandler.h +++ b/src/handlers/dns/v2/DnsStreamHandler.h @@ -506,7 +506,7 @@ class DnsStreamHandler final : public visor::StreamMetricsHandler _c_enabled; - uint16_t _f_rcode{0}; + std::vector _f_rcodes; uint64_t _f_answer_count{0}; std::vector _f_qnames; std::vector _f_qtypes; diff --git a/src/handlers/dns/v2/tests/test_dns_layer.cpp b/src/handlers/dns/v2/tests/test_dns_layer.cpp index c00de3d6a..16df6077f 100644 --- a/src/handlers/dns/v2/tests/test_dns_layer.cpp +++ b/src/handlers/dns/v2/tests/test_dns_layer.cpp @@ -331,7 +331,7 @@ TEST_CASE("DNS Filters: only_rcode nx", "[pcap][net]") REQUIRE(j["filtered_packets"] == 19); } -TEST_CASE("DNS Filters: only_rcode refused", "[pcap][dns]") +TEST_CASE("DNS Filters: only_rcode refused and nx", "[pcap][dns]") { PcapInputStream stream{"pcap-test"}; @@ -345,7 +345,7 @@ TEST_CASE("DNS Filters: only_rcode refused", "[pcap][dns]") c.config_set("num_periods", 1); DnsStreamHandler dns_handler{"dns-test", stream_proxy, &c}; - dns_handler.config_set("only_rcode", Refused); + dns_handler.config_set("only_rcode", {"nxdomain", "5"}); dns_handler.start(); stream.start(); @@ -356,11 +356,11 @@ TEST_CASE("DNS Filters: only_rcode refused", "[pcap][dns]") REQUIRE(counters.RNOERROR.value() == 0); REQUIRE(counters.SRVFAIL.value() == 0); REQUIRE(counters.REFUSED.value() == 1); - REQUIRE(counters.NX.value() == 0); + REQUIRE(counters.NX.value() == 1); REQUIRE(counters.NODATA.value() == 0); nlohmann::json j; dns_handler.metrics()->bucket(0)->to_json(j); - REQUIRE(j["filtered_packets"] == 19); + REQUIRE(j["filtered_packets"] == 17); } TEST_CASE("DNS Filters: only_qtypes AAAA and TXT", "[pcap][dns]") { @@ -773,7 +773,7 @@ TEST_CASE("DNS filter exceptions", "[pcap][dns][filter]") SECTION("only_rcode as string") { dns_handler.config_set("only_rcode", "1"); - REQUIRE_THROWS_WITH(dns_handler.start(), "DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer"); + REQUIRE_THROWS_WITH(dns_handler.start(), "DnsStreamHandler: wrong value type for only_rcode filter. It should be an integer or an array"); } SECTION("only_rcode invalid")