From: Remi Gacogne Date: Mon, 21 Jul 2025 09:56:47 +0000 (+0200) Subject: dnsdist: Support mnemonics for the Opcode selector X-Git-Tag: auth-5.1.0-alpha0~7^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1a37caf4bcef0ad8362ce5b2bf8f01e90c81986d;p=thirdparty%2Fpdns.git dnsdist: Support mnemonics for the Opcode selector Signed-off-by: Remi Gacogne --- diff --git a/pdns/dns.cc b/pdns/dns.cc index 7d1b772a2..df35dfa77 100644 --- a/pdns/dns.cc +++ b/pdns/dns.cc @@ -145,6 +145,16 @@ std::string Opcode::to_s(uint8_t opcode) { return s_opcodes.at(opcode); } +std::optional Opcode::from_lowercase_string(const std::string_view& opcode_string) +{ + static const std::array s_opcodes = { "query", "iquery", "status", "3", "notify", "update" }; + const auto* position = std::find(s_opcodes.begin(), s_opcodes.end(), opcode_string); + if (position == s_opcodes.end()) { + return std::nullopt; + } + return std::distance(s_opcodes.begin(), position); +} + // goal is to hash based purely on the question name, and turn error into 'default' uint32_t hashQuestion(const uint8_t* packet, uint16_t packet_len, uint32_t init, bool& wasOK) { diff --git a/pdns/dns.hh b/pdns/dns.hh index e1be00222..f1106828a 100644 --- a/pdns/dns.hh +++ b/pdns/dns.hh @@ -55,6 +55,7 @@ class Opcode public: enum opcodes_ : uint8_t { Query=0, IQuery=1, Status=2, Notify=4, Update=5 }; static std::string to_s(uint8_t opcode); + static std::optional from_lowercase_string(const std::string_view& opcode_string); }; // This needs to be a signed type, so that serialization of UnknownDomainID diff --git a/pdns/dnsdistdist/dnsdist-configuration-yaml.cc b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc index 998ee796d..b124b85c2 100644 --- a/pdns/dnsdistdist/dnsdist-configuration-yaml.cc +++ b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc @@ -154,6 +154,17 @@ static uint16_t strToQType(const std::string& context, const std::string& parame return qtype; } +static uint8_t strToOpcode(const std::string& context, const std::string& parameterName, const ::rust::String& opcode_rust_string) +{ + auto opcode_str = std::string(opcode_rust_string); + boost::to_lower(opcode_str); + auto opcode = Opcode::from_lowercase_string(opcode_str); + if (!opcode) { + return checkedConversionFromStr(context, parameterName, opcode_rust_string); + } + return *opcode; +} + static std::optional loadContentFromConfigurationFile(const std::string& fileName) { /* no check on the file size, don't do this with just any file! */ diff --git a/pdns/dnsdistdist/dnsdist-rules-generator.py b/pdns/dnsdistdist/dnsdist-rules-generator.py index f627a9959..885ad2320 100644 --- a/pdns/dnsdistdist/dnsdist-rules-generator.py +++ b/pdns/dnsdistdist/dnsdist-rules-generator.py @@ -68,6 +68,8 @@ def type_to_cpp(type_str, lua_interface, inside_container=False): return 'const std::string&' if type_str == 'RCode': return 'uint8_t' + if type_str == 'Opcode': + return 'uint8_t' return type_str def get_cpp_object_name(name, is_class=True): diff --git a/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-generator.py b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-generator.py index 74b9b6a9c..04529c4a7 100644 --- a/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-generator.py +++ b/pdns/dnsdistdist/dnsdist-rust-lib/dnsdist-settings-generator.py @@ -500,6 +500,8 @@ def get_cpp_parameters(struct_name, parameters, skip_name): field = f'convertSOAParams({field})' elif ptype == 'RCode': field = f'dnsdist::configuration::yaml::strToRCode("{struct_name}", "{name}", {field})' + elif ptype == 'Opcode': + field = f'dnsdist::configuration::yaml::strToOpcode("{struct_name}", "{name}", {field})' output += field return output diff --git a/pdns/dnsdistdist/dnsdist-selectors-definitions.yml b/pdns/dnsdistdist/dnsdist-selectors-definitions.yml index b957f4298..5714ff6cf 100644 --- a/pdns/dnsdistdist/dnsdist-selectors-definitions.yml +++ b/pdns/dnsdistdist/dnsdist-selectors-definitions.yml @@ -222,7 +222,8 @@ Set the ``source`` parameter to ``false`` to match against destination address i description: "Matches queries with opcode equals to ``code``" parameters: - name: "code" - type: "u8" + type: "Opcode" + rust-type: "String" description: "The opcode to match" - name: "Or" description: "Matches the traffic if one or more of the selectors Rules does match" diff --git a/pdns/test-dns_cc.cc b/pdns/test-dns_cc.cc index ca27b49d6..9eedc8c0d 100644 --- a/pdns/test-dns_cc.cc +++ b/pdns/test-dns_cc.cc @@ -25,6 +25,7 @@ #define BOOST_TEST_NO_MAIN +#include #include #include "dns.hh" @@ -68,9 +69,14 @@ BOOST_AUTO_TEST_CASE(test_opcode) for (uint8_t idx = Opcode::Query; idx <= Opcode::Update; idx++) { auto long_s = Opcode::to_s(idx); BOOST_CHECK(long_s.size() > 0); + boost::to_lower(long_s); + auto opcode = Opcode::from_lowercase_string(long_s); + BOOST_CHECK(opcode); + BOOST_CHECK_EQUAL(*opcode, idx); } BOOST_CHECK_EQUAL(Opcode::to_s(Opcode::Update + 1), std::to_string(Opcode::Update + 1)); + BOOST_CHECK(!Opcode::from_lowercase_string("notanopcode")); } BOOST_AUTO_TEST_CASE(test_resource_record_place) diff --git a/regression-tests.dnsdist/test_Yaml.py b/regression-tests.dnsdist/test_Yaml.py index bb12f6de0..c383f8856 100644 --- a/regression-tests.dnsdist/test_Yaml.py +++ b/regression-tests.dnsdist/test_Yaml.py @@ -391,3 +391,66 @@ query_rules: sender = getattr(self, method) (_, receivedResponse) = sender(query, response=None, useQueue=False) self.assertEqual(receivedResponse, expectedResponse) + +class TestYamlOpcode(DNSDistTest): + + _yaml_config_template = """--- +binds: + - listen_address: "127.0.0.1:%d" + protocol: Do53 + +backends: + - address: "127.0.0.1:%d" + protocol: Do53 + +query_rules: + - name: "refuse queries from specific opcode" + selector: + type: "Opcode" + code: "NOTIFY" + action: + type: "RCode" + rcode: "Refused" +""" + _yaml_config_params = ['_dnsDistPort', '_testServerPort'] + _config_params = [] + + def testRefuseOpcodeNotify(self): + """ + YAML: Refuse Opcode NOTIFY + """ + name = 'opcodenotify.yaml.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + query.set_opcode(dns.opcode.NOTIFY) + query.flags &= ~dns.flags.RD + expectedResponse = dns.message.make_response(query) + expectedResponse.set_rcode(dns.rcode.REFUSED) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + (_, receivedResponse) = sender(query, response=None, useQueue=False) + self.assertEqual(receivedResponse, expectedResponse) + + def testAllowOpcodeUpdate(self): + """ + YAML: Allow Opcode UPDATE + """ + name = 'opcodeupdate.yaml.tests.powerdns.com.' + query = dns.message.make_query(name, 'SOA', 'IN') + query.set_opcode(dns.opcode.UPDATE) + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + (receivedQuery, receivedResponse) = sender(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + self.assertEqual(query, receivedQuery) + self.assertEqual(response, receivedResponse)