]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: accept a table to SVCParams for newSVCRecordParameters 10733/head
authorPieter Lexis <pieter.lexis@powerdns.com>
Fri, 17 Sep 2021 14:35:56 +0000 (16:35 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Mon, 20 Sep 2021 09:25:37 +0000 (11:25 +0200)
.github/actions/spell-check/expect.txt
pdns/dnsdist-lua-bindings.cc
pdns/dnsdistdist/dnsdist-svc.cc
pdns/dnsdistdist/dnsdist-svc.hh
pdns/dnsdistdist/docs/reference/svc.rst
regression-tests.dnsdist/test_SVCB.py

index c4cf3345a51181e38ae419eb4f7f0d724dd59d7b..7bbaae96adfbbc30e9ec892d1d5b89d1ebcf387c 100644 (file)
@@ -1079,6 +1079,7 @@ Nixu
 nkey
 nmg
 Nncqx
+NNNN
 noaction
 noad
 noall
index cadf71f4c896854b72c4fd57c95b74c46b5f8bcf..f68f855b9bf7cc6c4d857c47d7aa4672bf084e6a 100644 (file)
@@ -536,47 +536,14 @@ void setupLuaBindings(LuaContext& luaCtx, bool client)
     return std::make_shared<DOHResponseMapEntry>(regex, status, PacketBuffer(content.begin(), content.end()), headers);
   });
 
-  luaCtx.writeFunction("newSVCRecordParameters", [](uint16_t priority, const std::string& target, const std::vector<std::pair<int, uint16_t>>& mandatoryParams, const std::vector<std::pair<int, std::string>>& alpns, bool noDefaultAlpn, boost::optional<uint16_t> port, const boost::optional<std::string> ech, boost::optional<std::vector<std::pair<int, std::string>>> ipv4hints, boost::optional<std::vector<std::pair<int, std::string>>> ipv6hints, boost::optional<std::vector<std::pair<int, std::string>>> additionalParameters)
+  luaCtx.writeFunction("newSVCRecordParameters", [](uint16_t priority, const std::string& target, boost::optional<svcParamsLua_t> additionalParameters)
   {
     SVCRecordParameters parameters;
-    parameters.priority = priority;
-    parameters.target = DNSName(target);
-
-    for (const auto& entry : mandatoryParams) {
-      parameters.mandatoryParams.insert(entry.second);
-    }
-
-    for (const auto& entry : alpns) {
-      parameters.alpns.push_back(entry.second);
-    }
-
-    parameters.noDefaultAlpn = noDefaultAlpn;
-
-    if (port) {
-      parameters.port = *port;
-    }
-
-    if (ech) {
-      parameters.ech = *ech;
-    }
-
-    if (ipv4hints) {
-      for (const auto& entry : *ipv4hints) {
-        parameters.ipv4hints.push_back(ComboAddress(entry.second));
-      }
-    }
-
-    if (ipv6hints) {
-      for (const auto& entry : *ipv6hints) {
-        parameters.ipv6hints.push_back(ComboAddress(entry.second));
-      }
-    }
-
     if (additionalParameters) {
-      for (const auto& entry : *additionalParameters) {
-        parameters.additionalParams.push_back({entry.first, entry.second});
-      }
+      parameters = parseSVCParameters(*additionalParameters);
     }
+    parameters.priority = priority;
+    parameters.target = DNSName(target);
 
     return parameters;
   });
index 66e66dc2ca1167373229c6961b25fb907acbe68b..ffd42fd649433594226f768fcf51e432874dc909 100644 (file)
@@ -91,3 +91,43 @@ bool generateSVCPayload(std::vector<uint8_t>& payload, const SVCRecordParameters
 {
   return generateSVCPayload(payload, parameters.priority, parameters.target, parameters.mandatoryParams, parameters.alpns, parameters.noDefaultAlpn, parameters.port, parameters.ech, parameters.ipv4hints, parameters.ipv6hints, parameters.additionalParams);
 }
+
+struct SVCRecordParameters parseSVCParameters(const svcParamsLua_t& params)
+{
+  struct SVCRecordParameters parameters;
+  for (const auto& p : params) {
+    if (p.first == "mandatory") {
+      for (auto const& entry : boost::get<std::vector<std::pair<int, std::string>>>(p.second)) {
+        parameters.mandatoryParams.insert(SvcParam::keyFromString(entry.second));
+      }
+    }
+    else if (p.first == "alpn") {
+      for (auto const& entry : boost::get<std::vector<std::pair<int, std::string>>>(p.second)) {
+        parameters.alpns.push_back(entry.second);
+      }
+    }
+    else if (p.first == "noDefaultAlpn") {
+      parameters.noDefaultAlpn = boost::get<bool>(p.second);
+    }
+    else if (p.first == "port") {
+      parameters.port = boost::get<uint16_t>(p.second);
+    }
+    else if (p.first == "ipv4hint") {
+      for (auto const& entry : boost::get<std::vector<std::pair<int, std::string>>>(p.second)) {
+        parameters.ipv4hints.push_back(ComboAddress(entry.second));
+      }
+    }
+    else if (p.first == "ech") {
+      parameters.ech = boost::get<std::string>(p.second);
+    }
+    else if (p.first == "ipv6hint") {
+      for (auto const& entry : boost::get<std::vector<std::pair<int, std::string>>>(p.second)) {
+        parameters.ipv6hints.push_back(ComboAddress(entry.second));
+      }
+    }
+    else {
+      parameters.additionalParams.push_back({SvcParam::keyFromString(p.first), boost::get<std::string>(p.second)});
+    }
+  }
+  return parameters;
+}
index fefd078c9dfa411bd9ac762edc817d3d6cb35434..d0a1a8c961b3da0660234518c7545a22d0ef274d 100644 (file)
@@ -25,6 +25,8 @@
 #include <set>
 #include <string>
 #include <vector>
+#include <unordered_map>
+#include <boost/variant.hpp>
 
 #include "dnsname.hh"
 #include "iputils.hh"
@@ -47,6 +49,18 @@ struct SVCRecordParameters
   bool noDefaultAlpn{false};
 };
 
+typedef std::unordered_map<
+  std::string,
+  boost::variant<
+    uint16_t,
+    bool,
+    std::string,
+    std::vector<std::pair<int, std::string>>,
+    std::vector<std::pair<int, ComboAddress>>>>
+  svcParamsLua_t;
+
+struct SVCRecordParameters parseSVCParameters(const svcParamsLua_t& params);
+
 bool generateSVCPayload(std::vector<uint8_t>& payload, uint16_t priority, const DNSName& target, const std::set<uint16_t>& mandatoryParams, const std::vector<std::string>& alpns, bool noDefaultAlpn, std::optional<uint16_t> port, const std::string& ech, const std::vector<ComboAddress>& ipv4hints, const std::vector<ComboAddress>& ipv6hints, const std::vector<std::pair<uint16_t, std::string>>& additionalParams);
 
 bool generateSVCPayload(std::vector<uint8_t>& payload, const SVCRecordParameters& parameters);
index d57ae538aa470963b15af218cde8764a44e0b0af..ceffead4fb887d6eecf3d18ee82e372fb7edaccd 100644 (file)
@@ -1,7 +1,7 @@
 SVCRecordParameters
 ===================
 
-.. function:: newSVCRecordParameters(priority, target, mandatoryParams, alpns, noDefaultAlpn [, port [, ech [, ipv4hints [, ipv6hints [, additionalParameters ]]]]]) -> SVCRecordParameters
+.. function:: newSVCRecordParameters(priority, target[, SVCParams]) -> SVCRecordParameters
 
   .. versionadded:: 1.7.0
 
@@ -10,21 +10,30 @@ SVCRecordParameters
   .. code-block:: Lua
 
     -- reply to SVCB queries for resolver.powerdns.com. indicating DoT on port 853 of dot.powerdns.com. (192.0.2.1/2001:db8::1), DoH on https://doh.powerdns.com/dns-query (192.0.2.2/2001:db8::2)
-    local svc = { newSVCRecordParameters(1, "dot.powerdns.com.", { 3 }, { "dot" }, false, 853, "", { "192.0.2.1" }, { "2001:db8::1" }),
-                  newSVCRecordParameters(2, "doh.powerdns.com.", { 3 }, { "h2" },  false, 443, "", { "192.0.2.2" }, { "2001:db8::2" }, { ["42"] = "/dns-query{?dns}" })
-                }    
+    local svc = { newSVCRecordParameters(1, "dot.powerdns.com.", { mandatory={"port"}, alpn={ "dot" }, noDefaultAlpn=true, port=853, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" } }),
+                  newSVCRecordParameters(2, "doh.powerdns.com.", { mandatory={"port"}, alpn={ "h2" }, port=443, ipv4hint={ "192.0.2.2" }, ipv6hint={ "2001:db8::2" }, key42 = "/dns-query{?dns}" })
+                }
     addAction(AndRule{QTypeRule(64), QNameRule('resolver.powerdns.com.')}, SpoofSVCAction(svc))
 
   :param int priority: The priority of this record. if more than one record is returned, they all should have different priorities. A priority of 0 indicates Alias mode and no other record should be present in the RRSet.
   :param str target: A domain name indicating the target name.
-  :param list of integers mandatoryParams: The numeric values of the supplied parameters that are mandatory for the client to understand.
-  :param list of strings alpns: The ALPN values, like "dot" or "h2".
-  :param bool noDefaultAlpn: Whether the default ALPN value should be ignored and replaced by the supplied ones.
-  :param int port: Optional port to connect to.
-  :param str ech: Optional Encrypted Client Hello value, as a raw string (null bytes are supported).
-  :param list of strings ipv4hints: Optional list of IPv4 addresses.
-  :param list of strings ipv6hints: Optional list of IPv6 addresses.
-  :param table of strings additionalParameters: Optional table of additionals parameters. The key should be numerical and will be used as the SvcParamKey, while the value should be a raw binary string (null bytes are supported) and will be passed as the SvcParamValue as-is.
+  :param table SVCParams: Optional table of additionals parameters. The key should be the name of the SVC parameter and will be used as the SvcParamKey, while the value depends on the key (see below)
+
+  These SVCParams can be set::
+
+    {
+      mandatory={STRING},   -- The mandatory keys. the table of strings must be the key names (like "port" and "key998").
+      alpn={STRING},        -- alpns for this record, like "doh" or "h2".
+      noDefaultAlpn=BOOL,   -- When true, the no-default-alpn key is included in the record, false or absent means it does not exist in the record.
+      port=NUM,             -- Port parameter to include.
+      ipv4hint={STRING},    -- IPv4 hints to include into the record.
+      ech=STRING,           -- Encrypted Client Hello as a raw string (can include null bytes).
+      ipv6hint={STRING}     -- IPv6 hints to include into the record.
+    }
+
+  Any other parameters can be set by using the ``keyNNNN`` syntax and must use a raw string. Like this::
+
+    key776="hello\0world"
 
 .. class:: SVCRecordParameters
 
index 6e572ab0198ec86fb9eb28f23433bd534f48809f..2da43c46e28ee7925ccea44d8898cca5edc6965f 100644 (file)
@@ -5,22 +5,22 @@ from dnsdisttests import DNSDistTest
 class TestSVCB(DNSDistTest):
 
     _config_template = """
-    local basicSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { 3 }, { "dot" }, true, 853, "whatever", { "192.0.2.1" }, { "2001:db8::1" }),
-                       newSVCRecordParameters(2, "doh.powerdns.com.", { 3 }, { "h2" },  false, 443, "whatever", { "192.0.2.2" }, { "2001:db8::2" }, { ["42"] = "/dns-query{?dns}" })
+    local basicSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { mandatory={"port"}, alpn={"dot"}, noDefaultAlpn=true, port=853, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" } }),
+                       newSVCRecordParameters(2, "doh.powerdns.com.", { mandatory={"port"}, alpn={"h2"}, port=443, ipv4hint={ "192.0.2.2" }, ipv6hint={ "2001:db8::2" }, key42="/dns-query{?dns}" })
                      }
     addAction(AndRule{QTypeRule(64), makeRule("basic.svcb.tests.powerdns.com.")}, SpoofSVCAction(basicSVC, {aa=true}))
 
-    local noHintsSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { 3 }, { "dot" }, true, 853),
-                         newSVCRecordParameters(2, "doh.powerdns.com.", { 3 }, { "h2" },  false, 443, "", { }, { }, { ["42"] = "/dns-query{?dns}" })
+    local noHintsSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { mandatory={"port"}, alpn={"dot"}, noDefaultAlpn=true, port=853}),
+                         newSVCRecordParameters(2, "doh.powerdns.com.", { mandatory={"port"}, alpn={"h2"}, port=443, key42="/dns-query{?dns}" })
                      }
     addAction(AndRule{QTypeRule(64), makeRule("no-hints.svcb.tests.powerdns.com.")}, SpoofSVCAction(noHintsSVC, {aa=true}))
 
-    local effectiveTargetSVC = { newSVCRecordParameters(1, ".", { 3 }, { "dot" }, true, 853, "", { "192.0.2.1" }, { "2001:db8::1" }),
-                                 newSVCRecordParameters(2, ".", { 3 }, { "h2" },  false, 443, "", { "192.0.2.1" }, { "2001:db8::1" }, { ["42"] = "/dns-query{?dns}" })
+    local effectiveTargetSVC = { newSVCRecordParameters(1, ".", { mandatory={"port"}, alpn={ "dot" }, noDefaultAlpn=true, port=853, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" }}),
+                                 newSVCRecordParameters(2, ".", { mandatory={"port"}, alpn={ "h2" }, port=443, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" }, key42="/dns-query{?dns}"})
                      }
     addAction(AndRule{QTypeRule(64), makeRule("effective-target.svcb.tests.powerdns.com.")}, SpoofSVCAction(effectiveTargetSVC, {aa=true}))
 
-    local httpsSVC = { newSVCRecordParameters(1, ".", { 3 }, { "h2" }, true, 8002, "...", { "192.0.2.2" }, { "2001:db8::2" }) }
+    local httpsSVC = { newSVCRecordParameters(1, ".", { mandatory={"port"}, alpn={ "h2" }, noDefaultAlpn=true, port=8002, ipv4hint={ "192.0.2.2" }, ipv6hint={ "2001:db8::2" }}) }
     addAction(AndRule{QTypeRule(65), makeRule("https.svcb.tests.powerdns.com.")}, SpoofSVCAction(httpsSVC))
 
     newServer{address="127.0.0.1:%s"}