--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "svc-records.hh"
+#include "misc.hh"
+#include "base64.hh"
+
+const std::map<std::string, SvcParam::SvcParamKey> SvcParam::SvcParams = {
+ {"mandatory", SvcParam::SvcParamKey::mandatory},
+ {"alpn", SvcParam::SvcParamKey::alpn},
+ {"no-default-alpn", SvcParam::SvcParamKey::no_default_alpn},
+ {"port", SvcParam::SvcParamKey::port},
+ {"ipv4hint", SvcParam::SvcParamKey::ipv4hint},
+ {"echconfig", SvcParam::SvcParamKey::echconfig},
+ {"ipv6hint", SvcParam::SvcParamKey::ipv6hint}
+};
+
+SvcParam::SvcParamKey SvcParam::keyFromString(const std::string& k) {
+ auto it = SvcParams.find(k);
+ if (it != SvcParams.end()) {
+ return it->second;
+ }
+ if (k.substr(0, 3) == "key") {
+ try {
+ return SvcParam::SvcParamKey(pdns_stou(k.substr(3)));
+ }
+ catch (...) {
+ }
+ }
+ throw std::invalid_argument("SvcParam '" + k + "'is not recognized or in keyNNNN format");
+}
+
+std::string SvcParam::keyToString(const SvcParam::SvcParamKey& k) {
+ auto ret = std::find_if(SvcParams.begin(), SvcParams.end(), [&](const std::pair<std::string, SvcParam::SvcParamKey>& e) { return e.second == k; });
+ if (ret != SvcParams.end()) {
+ return ret->first;
+ }
+ return "key" + std::to_string(k);
+}
+
+SvcParam::SvcParam() {};
+
+SvcParam::SvcParam(const SvcParamKey &key) {
+ d_key = key;
+ if (d_key != SvcParamKey::no_default_alpn) {
+ throw std::invalid_argument("can not create non-empty SvcParam for key '" + keyToString(key) + "'");
+ }
+}
+
+SvcParam::SvcParam(const SvcParamKey &key, const std::string &value) {
+ d_key = key;
+ if (d_key != SvcParamKey::echconfig && d_key < 7) {
+ throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with a string value");
+ }
+ if (d_key == SvcParamKey::echconfig) {
+ std::string d;
+ // TODO check Base64 decode
+ d_echconfig = value;
+ return;
+ }
+ d_value = value;
+}
+
+SvcParam::SvcParam(const SvcParamKey &key, const std::set<std::string> &value) {
+ d_key = key;
+ if (d_key != SvcParamKey::alpn && d_key != SvcParamKey::mandatory) {
+ throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with a string-set value");
+ }
+ if (d_key == SvcParamKey::alpn) {
+ d_alpn = value;
+ }
+ if (d_key == SvcParamKey::mandatory) {
+ // TODO validate entries
+ d_mandatory = value;
+ }
+}
+
+SvcParam::SvcParam(const SvcParamKey &key, const std::set<ComboAddress> &value) {
+ d_key = key;
+ if (d_key != SvcParamKey::ipv6hint && d_key != SvcParamKey::ipv4hint) {
+ throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with an IP address value");
+ }
+ for (auto const &addr : value) {
+ if (d_key == SvcParam::ipv6hint && !addr.isIPv6()) {
+ throw std::invalid_argument("non-IPv6 address ('" + addr.toString() + "') passed for " + keyToString(key));
+ }
+ if (d_key == SvcParam::ipv4hint && !addr.isIPv4()) {
+ throw std::invalid_argument("non-IPv4 address ('" + addr.toString() + "') passed for " + keyToString(key));
+ }
+ }
+ d_ipHints = value;
+}
+
+SvcParam::SvcParam(const SvcParamKey &key, const uint16_t value) {
+ d_key = key;
+ if (d_key != SvcParamKey::port) {
+ throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with an port value");
+ }
+ d_port = value;
+}
+
+std::set<ComboAddress> SvcParam::getIPHints() const {
+ if (d_key != SvcParamKey::ipv6hint && d_key != SvcParamKey::ipv4hint) {
+ throw std::invalid_argument("getIPHints called for non-IP address key '" + keyToString(d_key) + "'");
+ }
+ return d_ipHints;
+}
+
+uint16_t SvcParam::getPort() const {
+ if (d_key != SvcParam::port) {
+ throw std::invalid_argument("getPort called for non-port key '" + keyToString(d_key) + "'");
+ }
+ return d_port;
+}
+
+std::set<std::string> SvcParam::getALPN() const {
+ if (d_key != SvcParam::alpn) {
+ throw std::invalid_argument("getALPN called for non-alpn key '" + keyToString(d_key) + "'");
+ }
+ return d_alpn;
+}
+
+std::set<std::string> SvcParam::getMandatory() const {
+ if (d_key != SvcParam::mandatory) {
+ throw std::invalid_argument("getMandatory called for non-mandatory key '" + keyToString(d_key) + "'");
+ }
+ return d_mandatory;
+}
+
+std::string SvcParam::getEchConfig() const {
+ if (d_key != SvcParam::echconfig) {
+ throw std::invalid_argument("getEchConfig called for non-echconfig key '" + keyToString(d_key) + "'");
+ }
+ return d_echconfig;
+}
+
+std::string SvcParam::getValue() const {
+ if (d_key < 7) {
+ throw std::invalid_argument("getValue called for non-single value key '" + keyToString(d_key) + "'");
+ }
+ return d_value;
+}
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include <string>
+#include <map>
+#include <set>
+#include "iputils.hh"
+
+class SvcParam {
+ public:
+ enum SvcParamKey: uint16_t {
+ // TODO link to IANA registry
+ /* When adding new values, you *must* update SvcParam::SvcParam(const std::string &key, const std::string &value)
+ * in svc-record.cc with the new numbers
+ */
+ mandatory = 0,
+ alpn = 1,
+ no_default_alpn = 2,
+ port = 3,
+ ipv4hint = 4,
+ echconfig = 5,
+ ipv6hint = 6
+ };
+
+ //! empty Param, unusable
+ SvcParam();
+
+ //! To create a value-less SvcParam (like no-default-alpn)
+ SvcParam(const SvcParamKey &key);
+
+ //! To create a "generic" SvcParam (for keyNNNNN and echconfig)
+ SvcParam(const SvcParamKey &key, const std::string &value);
+
+ //! To create a multi-value SvcParam (like alpn and mandatory)
+ SvcParam(const SvcParamKey &key, const std::set<std::string> &value);
+
+ //! To create and ipv{4,6}hists SvcParam
+ SvcParam(const SvcParamKey &key, const std::set<ComboAddress> &value);
+
+ //! To create a port SvcParam
+ SvcParam(const SvcParamKey &key, const uint16_t value);
+
+ //! Returns the SvcParamKey based on the input
+ static SvcParamKey keyFromString(const std::string &k);
+
+ //! Returns the string value of the SvcParamKey
+ static std::string keyToString(const SvcParamKey &k);
+
+ bool operator< (const SvcParam &other) const {
+ return this->d_key < other.d_key;
+ };
+
+ SvcParamKey getKey() const {
+ return d_key;
+ }
+
+ uint16_t getPort() const;
+ std::set<ComboAddress> getIPHints() const;
+ std::set<std::string> getALPN() const;
+ std::set<std::string> getMandatory() const;
+ std::string getEchConfig() const;
+ std::string getValue() const;
+
+ private:
+ SvcParamKey d_key;
+ std::string d_value; // For keyNNNNN vals
+
+ std::set<std::string> d_alpn; // For ALPN
+ std::set<std::string> d_mandatory; // For mandatory
+ std::set<ComboAddress> d_ipHints; // For ipv{6,4}hints
+ std::string d_echconfig; // For echconfig
+ uint16_t d_port; // For port
+
+ static const std::map<std::string, SvcParamKey> SvcParams;
+};
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+#include <bitset>
+#include "svc-records.hh"
+#include "base64.hh"
+
+using namespace boost;
+
+BOOST_AUTO_TEST_SUITE(test_svc_records_cc)
+BOOST_AUTO_TEST_CASE(test_SvcParam_keyFromString) {
+ SvcParam::SvcParamKey k;
+
+ k = SvcParam::keyFromString("mandatory");
+ BOOST_CHECK_EQUAL(k, 0);
+ BOOST_CHECK_EQUAL(k, SvcParam::mandatory);
+
+ k = SvcParam::keyFromString("alpn");
+ BOOST_CHECK_EQUAL(k, 1);
+ BOOST_CHECK_EQUAL(k, SvcParam::alpn);
+
+ k = SvcParam::keyFromString("no-default-alpn");
+ BOOST_CHECK_EQUAL(k, 2);
+ BOOST_CHECK_EQUAL(k, SvcParam::no_default_alpn);
+
+ k = SvcParam::keyFromString("port");
+ BOOST_CHECK_EQUAL(k, 3);
+ BOOST_CHECK_EQUAL(k, SvcParam::port);
+
+ k = SvcParam::keyFromString("ipv4hint");
+ BOOST_CHECK_EQUAL(k, 4);
+ BOOST_CHECK_EQUAL(k, SvcParam::ipv4hint);
+
+ k = SvcParam::keyFromString("echconfig");
+ BOOST_CHECK_EQUAL(k, 5);
+ BOOST_CHECK_EQUAL(k, SvcParam::echconfig);
+
+ k = SvcParam::keyFromString("ipv6hint");
+ BOOST_CHECK_EQUAL(k, 6);
+ BOOST_CHECK_EQUAL(k, SvcParam::ipv6hint);
+
+ k = SvcParam::keyFromString("key0");
+ BOOST_CHECK_EQUAL(k, 0);
+ BOOST_CHECK_EQUAL(k, SvcParam::mandatory);
+
+ k = SvcParam::keyFromString("key666");
+ BOOST_CHECK_EQUAL(k, 666);
+
+ BOOST_CHECK_THROW(SvcParam::keyFromString("MANDATORY"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(test_SvcParam_keyToString) {
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::mandatory), "mandatory");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::alpn), "alpn");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::no_default_alpn), "no-default-alpn");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::port), "port");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::ipv4hint), "ipv4hint");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::echconfig), "echconfig");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::ipv6hint), "ipv6hint");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::SvcParamKey(7)), "key7");
+ BOOST_CHECK_EQUAL(SvcParam::keyToString(SvcParam::SvcParamKey(666)), "key666");
+}
+
+BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_no_value) {
+ BOOST_CHECK_NO_THROW(SvcParam(SvcParam::no_default_alpn));
+ BOOST_CHECK_THROW(SvcParam(SvcParam::alpn), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::keyFromString("key666")), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_value) {
+ string val = "foobar";
+ auto base64val = Base64Encode(val);
+ SvcParam param;
+
+ BOOST_CHECK_THROW(SvcParam(SvcParam::mandatory, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::alpn, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::no_default_alpn, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::port, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv4hint, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv6hint, val), std::invalid_argument);
+
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::echconfig, base64val));
+ BOOST_CHECK_EQUAL(param.getEchConfig(), base64val);
+ BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getALPN(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getIPHints(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getMandatory(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getPort(), std::invalid_argument);
+
+ // TODO test bad base64 value
+ // BOOST_CHECK_THROW(SvcParam(SvcParam::echconfig, val), std::invalid_argument);
+
+ // Any string is allowed.....
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::keyFromString("key666"), base64val));
+ BOOST_CHECK_EQUAL(param.getValue(), base64val);
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::keyFromString("key666"), val));
+ BOOST_CHECK_EQUAL(param.getValue(), val);
+
+ BOOST_CHECK_THROW(param.getALPN(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getIPHints(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getMandatory(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getPort(), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_set_string_value) {
+ set<string> val({"foo", "bar", "baz"});
+
+ BOOST_CHECK_THROW(SvcParam(SvcParam::no_default_alpn, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::port, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv4hint, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::echconfig, val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv6hint, val), std::invalid_argument);
+
+ SvcParam param;
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::keyFromString("mandatory"), val)); // TODO this will fail once we start checking the contents
+
+ auto retval = param.getMandatory();
+ BOOST_CHECK_EQUAL_COLLECTIONS(retval.begin(), retval.end(), val.begin(), val.end());
+ BOOST_CHECK_THROW(param.getALPN(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getIPHints(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getPort(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
+
+ retval.clear();
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::keyFromString("alpn"), val));
+ retval = param.getALPN();
+ BOOST_CHECK_EQUAL_COLLECTIONS(retval.begin(), retval.end(), val.begin(), val.end());
+ BOOST_CHECK_THROW(param.getMandatory(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getIPHints(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getPort(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_set_comboaddress_value) {
+ ComboAddress ca1("192.0.2.1");
+ ComboAddress ca2("192.0.2.2");
+ ComboAddress ca3("2001:db8::1");
+ ComboAddress ca4("2001:db8::2");
+
+ set<ComboAddress> mixedVal({ca1, ca3});
+ set<ComboAddress> v4Val({ca1, ca2});
+ set<ComboAddress> v6Val({ca3, ca4});
+
+ BOOST_CHECK_THROW(SvcParam(SvcParam::mandatory, v4Val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::alpn, v4Val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::no_default_alpn, v4Val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::port, v4Val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::echconfig, v4Val), std::invalid_argument);
+
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv6hint, v4Val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv4hint, v6Val), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv6hint, mixedVal), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv4hint, mixedVal), std::invalid_argument);
+
+ SvcParam param;
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::ipv4hint, v4Val));
+
+ auto retval = param.getIPHints();
+ BOOST_CHECK(retval == v4Val);
+ BOOST_CHECK_THROW(param.getMandatory(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getALPN(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getPort(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
+
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::ipv6hint, v6Val));
+ retval.clear();
+ retval = param.getIPHints();
+ BOOST_CHECK(retval == v6Val);
+ BOOST_CHECK_THROW(param.getMandatory(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getALPN(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getPort(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_uint16_value) {
+ uint16_t port(53);
+
+ BOOST_CHECK_THROW(SvcParam(SvcParam::mandatory, port), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::alpn, port), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::no_default_alpn, port), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv4hint, port), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::echconfig, port), std::invalid_argument);
+ BOOST_CHECK_THROW(SvcParam(SvcParam::ipv6hint, port), std::invalid_argument);
+
+ SvcParam param;
+ BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::port, port));
+ BOOST_CHECK_EQUAL(param.getPort(), port);
+ BOOST_CHECK_THROW(param.getMandatory(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getALPN(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getIPHints(), std::invalid_argument);
+ BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
\ No newline at end of file