]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
SVCB: Accept known SVCParams in generic format 10727/head
authorPieter Lexis <pieter.lexis@powerdns.com>
Wed, 15 Sep 2021 12:05:18 +0000 (14:05 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Mon, 20 Sep 2021 08:41:24 +0000 (10:41 +0200)
pdns/rcpgenerator.cc
pdns/svc-records.cc
pdns/svc-records.hh
pdns/test-rcpgenerator_cc.cc

index ddda75c9eb0df1cc7a72263ed6eaf7be33078cf4..43bc488ce9b904c3d3d1bc0f93c06d63c00d76a4 100644 (file)
@@ -347,8 +347,9 @@ void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
     // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
     string k = d_string.substr(pos, d_pos - pos);
     SvcParam::SvcParamKey key;
+    bool generic;
     try {
-      key = SvcParam::keyFromString(k);
+      key = SvcParam::keyFromString(k, generic);
     } catch (const std::invalid_argument &e) {
       throw RecordTextException(e.what());
     }
@@ -372,11 +373,22 @@ void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
       break;
     case SvcParam::ipv4hint: /* fall-through */
     case SvcParam::ipv6hint: {
-      vector<string> value;
-      xfrSVCBValueList(value);
       vector<ComboAddress> hints;
       bool doAuto{false};
-      try {
+      if (generic) {
+        string value;
+        xfrRFC1035CharString(value);
+        size_t len = key == SvcParam::ipv4hint ? 4 : 16;
+        if (value.size() % len != 0) {
+          throw RecordTextException(k + " in generic format has wrong number of bytes");
+        }
+        for (size_t i=0; i<value.size(); i += len) {
+          auto hint = makeComboAddressFromRaw(static_cast<uint8_t>(key), &value.at(i), len);
+          hints.push_back(hint);
+        }
+      } else {
+        vector<string> value;
+        xfrSVCBValueList(value);
         for (auto const &v: value) {
           if (v == "auto") {
             doAuto = true;
@@ -385,6 +397,8 @@ void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
           }
           hints.push_back(ComboAddress(v));
         }
+      }
+      try {
         auto p = SvcParam(key, std::move(hints));
         p.setAutoHint(doAuto);
         val.insert(p);
@@ -396,11 +410,41 @@ void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
     }
     case SvcParam::alpn: {
       vector<string> value;
-      xfrSVCBValueList(value);
+      if (generic) {
+        string v;
+        xfrRFC1035CharString(v);
+        size_t spos{0}, len;
+        while (spos < v.length()) {
+          len = v.at(spos);
+          spos += 1;
+          if (len > v.length() - spos) {
+            throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
+          }
+          value.push_back(v.substr(spos, len));
+          spos += len;
+        }
+      } else {
+        xfrSVCBValueList(value);
+      }
       val.insert(SvcParam(key, std::move(value)));
       break;
     }
     case SvcParam::mandatory: {
+      if (generic) {
+        string v;
+        xfrRFC1035CharString(v);
+        if (v.length() % 2 != 0) {
+          throw RecordTextException("Wrong number of bytes in SVC Param " + k);
+        }
+        std::set<SvcParam::SvcParamKey> keys;
+        for (size_t i=0; i < v.length(); i += 2) {
+          uint16_t mand = (v.at(i) << 8);
+          mand += v.at(i+1);
+          keys.insert(SvcParam::SvcParamKey(mand));
+        }
+        val.insert(SvcParam(key, std::move(keys)));
+        break;
+      }
       vector<string> parts;
       xfrSVCBValueList(parts);
       set<string> values(parts.begin(), parts.end());
@@ -409,22 +453,36 @@ void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
     }
     case SvcParam::port: {
       uint16_t port;
-      xfr16BitInt(port);
+      if (generic) {
+        string v;
+        xfrRFC1035CharString(v);
+        if (v.length() != 2) {
+          throw RecordTextException("port in generic format has the wrong length, expected 2, got " + std::to_string(v.length()));
+        }
+        port = (v.at(0) << 8);
+        port += v.at(1);
+      } else {
+        xfr16BitInt(port);
+      }
       val.insert(SvcParam(key, port));
       break;
     }
     case SvcParam::ech: {
-      bool haveQuote = d_string.at(d_pos) == '"';
-      if (haveQuote) {
-        d_pos++;
-      }
       string value;
-      xfrBlobNoSpaces(value);
-      if (haveQuote) {
-        if (d_string.at(d_pos) != '"') {
-          throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
+      if (generic) {
+        xfrRFC1035CharString(value);
+      } else {
+        bool haveQuote = d_string.at(d_pos) == '"';
+        if (haveQuote) {
+          d_pos++;
+        }
+        xfrBlobNoSpaces(value);
+        if (haveQuote) {
+          if (d_string.at(d_pos) != '"') {
+            throw RecordTextException("ech value starts, but does not end with a '\"' symbol");
+          }
+          d_pos++;
         }
-        d_pos++;
       }
       val.insert(SvcParam(key, value));
       break;
index 90d1e77a982900f0499e1b95a0ba989cb0805d6f..d802aca11a335e6b6b4c38a06d71516d04314626 100644 (file)
@@ -34,12 +34,19 @@ const std::map<std::string, SvcParam::SvcParamKey> SvcParam::SvcParams = {
 };
 
 SvcParam::SvcParamKey SvcParam::keyFromString(const std::string& k) {
+  bool ignored;
+  return SvcParam::keyFromString(k, ignored);
+}
+
+SvcParam::SvcParamKey SvcParam::keyFromString(const std::string& k, bool &generic) {
   auto it = SvcParams.find(k);
   if (it != SvcParams.end()) {
+    generic = false;
     return it->second;
   }
   if (k.substr(0, 3) == "key") {
     try {
+      generic = true;
       return SvcParam::SvcParamKey(pdns_stou(k.substr(3)));
     }
     catch (...) {
@@ -104,7 +111,7 @@ SvcParam::SvcParam(const SvcParamKey &key, std::set<std::string> &&value) {
 SvcParam::SvcParam(const SvcParamKey &key, std::set<SvcParam::SvcParamKey> &&value) {
   d_key = key;
   if (d_key != SvcParamKey::mandatory) {
-    throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with a string-set value");
+    throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with a SvcParamKey-set value");
   }
   d_mandatory = std::move(value);
 }
index 5f154c99f8fb1d01ecd3cc58d805d51c71b77121..d195d646b11b185507f84fffea5c091746f3286e 100644 (file)
@@ -68,6 +68,9 @@ class SvcParam {
   //! Returns the SvcParamKey based on the input
   static SvcParamKey keyFromString(const std::string &k);
 
+  //! Returns the SvcParamKey based on the input, generic is true when the format was 'keyNNNN'
+  static SvcParamKey keyFromString(const std::string &k, bool &generic);
+
   //! Returns the string value of the SvcParamKey
   static std::string keyToString(const SvcParamKey &k);
 
index c2ea8ea4f4f00403ee7b43386749612cc5fd7caa..0987108e228777a636aa07e7f7e9af6204859294 100644 (file)
@@ -67,6 +67,28 @@ BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_alpn) {
         RecordTextWriter rtw2(target);
         rtw2.xfrSvcParamKeyVals(v);
         BOOST_CHECK_EQUAL(target, source);
+
+        // Check generic
+        v.clear();
+        source="key1=\\002h2\\002h3";
+        RecordTextReader rtr3(source);
+        rtr3.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        alpn = v.begin()->getALPN();
+        BOOST_CHECK_EQUAL(alpn.size(), 2U);
+        val = alpn.begin();
+        BOOST_CHECK_EQUAL(*val, "h2");
+        val++;
+        BOOST_CHECK_EQUAL(*val, "h3");
+
+        // Error conditions
+        source="key1=\\002h2\\003h3"; // Wrong length for 2nd argument
+        RecordTextReader rtr4(source);
+        BOOST_CHECK_THROW(rtr4.xfrSvcParamKeyVals(v), RecordTextException);
+
+        source="key1=\\002h2\\002h3foobar"; // extra data
+        RecordTextReader rtr5(source);
+        BOOST_CHECK_THROW(rtr5.xfrSvcParamKeyVals(v), RecordTextException);
 }
 
 BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_mandatory) {
@@ -103,6 +125,35 @@ BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_mandatory) {
         RecordTextWriter rtw2(target);
         rtw2.xfrSvcParamKeyVals(v);
         BOOST_CHECK_EQUAL(target, source);
+
+        // Generic parsing
+        v.clear();
+        source = "key0=\\000\\001\\000\\004";
+        RecordTextReader rtr3(source);
+        rtr3.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        m = v.begin()->getMandatory();
+        BOOST_CHECK_EQUAL(m.size(), 2U);
+        val = m.begin();
+        BOOST_CHECK(*val == SvcParam::alpn);
+        val++;
+        BOOST_CHECK(*val ==  SvcParam::ipv4hint);
+
+        // Broken
+        v.clear();
+        source = "key0=\\000\\001\\000";
+        RecordTextReader rtr4(source);
+        BOOST_CHECK_THROW(rtr4.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        source = "key0=";
+        RecordTextReader rtr5(source);
+        BOOST_CHECK_THROW(rtr5.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        source = "key0";
+        RecordTextReader rtr6(source);
+        BOOST_CHECK_THROW(rtr6.xfrSvcParamKeyVals(v), RecordTextException);
 }
 
 BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_no_default_alpn) {
@@ -123,6 +174,15 @@ BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_no_default_alpn) {
         RecordTextReader rtr2("no-default-alpn=");
         v.clear();
         BOOST_CHECK_THROW(rtr2.xfrSvcParamKeyVals(v), RecordTextException);
+
+        // Generic
+        v.clear();
+        source = "key2";
+        RecordTextReader rtr3(source);
+        rtr3.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::no_default_alpn);
 }
 
 BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv4hint) {
@@ -173,6 +233,33 @@ BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv4hint) {
         v.clear();
         RecordTextReader rtr4("ipv4hint=192.0.2.1,2001:db8::1");
         BOOST_CHECK_THROW(rtr4.xfrSvcParamKeyVals(v), RecordTextException);
+
+        // Check if we can parse the generic format
+        v.clear();
+        source = "key4=\\192\\000\\002\\015";
+        RecordTextReader rtr5(source);
+        rtr5.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().size(), 1U);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv4hint);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().begin()->toString(), "192.0.2.15");
+
+        v.clear();
+        source = "key4=\\192\\000\\002\\015\\192\\000\\002\\222";
+        RecordTextReader rtr6(source);
+        rtr6.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().size(), 2U);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv4hint);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().begin()->toString(), "192.0.2.15");
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().at(1).toString(), "192.0.2.222");
+
+        v.clear();
+        source = "key4=\\192\\000\\222"; // Wrong number of octets
+        RecordTextReader rtr7(source);
+        BOOST_CHECK_THROW(rtr7.xfrSvcParamKeyVals(v), RecordTextException);
 }
 
 BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv6hint) {
@@ -223,6 +310,32 @@ BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv6hint) {
         v.clear();
         RecordTextReader rtr4("ipv6hint=192.0.2.1,2001:db8::1");
         BOOST_CHECK_THROW(rtr4.xfrSvcParamKeyVals(v), RecordTextException);
+
+        // Check if we can parse the generic format
+        v.clear();
+        RecordTextReader rtr5("key6=\\032\\001\\013\\184\\000\\083\\000\\000\\000\\000\\000\\000\\000\\000\\000\\021");
+        rtr5.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().size(), 1U);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv6hint);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().begin()->toString(), "2001:db8:53::15");
+
+        v.clear();
+        source = "key6=\\032\\001\\013\\184\\000\\083\\000\\000\\000\\000\\000\\000\\000\\000\\000\\021\\032\\001\\013\\184\\000\\083\\000\\000\\000\\000\\000\\000\\000\\000\\000\\022";
+        RecordTextReader rtr6(source);
+        rtr6.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().size(), 2U);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv6hint);
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().begin()->toString(), "2001:db8:53::15");
+        BOOST_CHECK_EQUAL(v.begin()->getIPHints().at(1).toString(), "2001:db8:53::16");
+
+        v.clear();
+        source = "key6=\\040\\001\\015\\270\\000\\123\\000\\000\\000\\000\\000\\000\\000\\000\\000"; // wrong number of octets
+        RecordTextReader rtr7(source);
+        BOOST_CHECK_THROW(rtr7.xfrSvcParamKeyVals(v), RecordTextException);
 }
 
 BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_port) {
@@ -257,6 +370,20 @@ BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_port) {
         v.clear();
         RecordTextReader rtr5("port");
         BOOST_CHECK_THROW(rtr5.xfrSvcParamKeyVals(v), RecordTextException);
+
+        // Generic
+        v.clear();
+        RecordTextReader rtr6("key3");
+        BOOST_CHECK_THROW(rtr6.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        RecordTextReader rtr7("key3=\\000\\053");
+        rtr7.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::port);
+        val = v.begin()->getPort();
+        BOOST_CHECK_EQUAL(val, 53);
 }
 
 BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_generic) {
@@ -378,6 +505,17 @@ BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ech) {
         RecordTextWriter rtw(target);
         rtw.xfrSvcParamKeyVals(v);
         BOOST_CHECK_EQUAL(source, target);
+
+        // Generic
+        v.clear();
+        source = "key5=echconfig";
+        RecordTextReader rtr2(source);
+        rtr2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1U);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ech);
+        val = v.begin()->getECH();
+        BOOST_CHECK_EQUAL(val, "echconfig");
 }
 
 BOOST_AUTO_TEST_CASE(test_xfrNodeOrLocatorID) {