From 76a9865208daaa48040a3c0e79fc4924c438106e Mon Sep 17 00:00:00 2001 From: Pieter Lexis Date: Wed, 15 Sep 2021 14:05:18 +0200 Subject: [PATCH] SVCB: Accept known SVCParams in generic format --- pdns/rcpgenerator.cc | 88 ++++++++++++++++++---- pdns/svc-records.cc | 9 ++- pdns/svc-records.hh | 3 + pdns/test-rcpgenerator_cc.cc | 138 +++++++++++++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 16 deletions(-) diff --git a/pdns/rcpgenerator.cc b/pdns/rcpgenerator.cc index ddda75c9eb..43bc488ce9 100644 --- a/pdns/rcpgenerator.cc +++ b/pdns/rcpgenerator.cc @@ -347,8 +347,9 @@ void RecordTextReader::xfrSvcParamKeyVals(set& 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& val) break; case SvcParam::ipv4hint: /* fall-through */ case SvcParam::ipv6hint: { - vector value; - xfrSVCBValueList(value); vector 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(key), &value.at(i), len); + hints.push_back(hint); + } + } else { + vector value; + xfrSVCBValueList(value); for (auto const &v: value) { if (v == "auto") { doAuto = true; @@ -385,6 +397,8 @@ void RecordTextReader::xfrSvcParamKeyVals(set& 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& val) } case SvcParam::alpn: { vector 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 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 parts; xfrSVCBValueList(parts); set values(parts.begin(), parts.end()); @@ -409,22 +453,36 @@ void RecordTextReader::xfrSvcParamKeyVals(set& 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; diff --git a/pdns/svc-records.cc b/pdns/svc-records.cc index 90d1e77a98..d802aca11a 100644 --- a/pdns/svc-records.cc +++ b/pdns/svc-records.cc @@ -34,12 +34,19 @@ const std::map 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 &&value) { SvcParam::SvcParam(const SvcParamKey &key, std::set &&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); } diff --git a/pdns/svc-records.hh b/pdns/svc-records.hh index 5f154c99f8..d195d646b1 100644 --- a/pdns/svc-records.hh +++ b/pdns/svc-records.hh @@ -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); diff --git a/pdns/test-rcpgenerator_cc.cc b/pdns/test-rcpgenerator_cc.cc index c2ea8ea4f4..0987108e22 100644 --- a/pdns/test-rcpgenerator_cc.cc +++ b/pdns/test-rcpgenerator_cc.cc @@ -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) { -- 2.47.2