From 9f4f6ad76b261f60c3fe71e2c3d9543ec8d3dd4f Mon Sep 17 00:00:00 2001 From: Jasper Insinger Date: Tue, 13 Aug 2024 21:59:36 +0200 Subject: [PATCH] Format CAA, MX, SOA --- include/dns_json_defs.h | 31 ++++++++++++++-- include/dns_struct_defs.h | 1 + src/dns_packet.cpp | 17 +++++++++ test/utils_test.cc | 38 ++++++++++++++++++-- utils/encode_dns_char_string.h | 64 ++++++++++++++++++++++++++++++++++ utils/fixed_name.hpp | 18 +++++++++- 6 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 utils/encode_dns_char_string.h diff --git a/include/dns_json_defs.h b/include/dns_json_defs.h index 32a8b25..2a2b330 100644 --- a/include/dns_json_defs.h +++ b/include/dns_json_defs.h @@ -9,6 +9,7 @@ #include "dns_format.h" #include "dns_packet_constructor.h" #include "dns_struct_defs.h" +#include "encode_dns_char_string.h" /** * Definitions for resource records @@ -29,6 +30,13 @@ struct glz::meta { static constexpr auto value{&NSRdata::nameserver}; }; +template <> +struct glz::meta { + static constexpr auto value = [](const auto &self) -> auto { + return fmt::format("{} {}", self.preference, self.mailserver); + }; +}; + template <> struct glz::meta { static constexpr auto value{&CNAMERdata::cname}; @@ -46,7 +54,24 @@ struct glz::meta { template <> struct glz::meta { - static constexpr auto value{&TXTRdata::txt}; + static constexpr auto value = [](const auto &self) -> auto { + return encode_dns_char_string(self.txt); + }; +}; + +template <> +struct glz::meta { + static constexpr auto value = [](const auto &self) -> auto { + return fmt::format("{} {} {} {} {} {} {}", self.m_name, self.r_name, self.interval_settings.serial, self.interval_settings.refresh, self.interval_settings.retry, self.interval_settings.expire, self.interval_settings.minimum); + }; +}; + +template <> +struct glz::meta { + static constexpr auto value = [](const auto &self) -> auto { + auto value_encoded = encode_dns_char_string(self.value, true); + return fmt::format("{} {} {}", self.flags, self.tag, value_encoded); + }; }; template <> @@ -62,7 +87,7 @@ struct glz::meta { template <> struct glz::meta { - static constexpr auto value = [](auto &self) -> auto { + static constexpr auto value = [](const auto &self) -> auto { FixedName val; inet_ntop(AF_INET, &self, val.buf.data(), INET_ADDRSTRLEN); @@ -73,7 +98,7 @@ struct glz::meta { template <> struct glz::meta { - static constexpr auto value = [](auto &self) -> auto { + static constexpr auto value = [](const auto &self) -> auto { FixedName val; inet_ntop(AF_INET6, &self, val.buf.data(), INET6_ADDRSTRLEN); diff --git a/include/dns_struct_defs.h b/include/dns_struct_defs.h index b61a28c..eba1bdc 100644 --- a/include/dns_struct_defs.h +++ b/include/dns_struct_defs.h @@ -32,6 +32,7 @@ enum class DNSParseError { SrcPortErr, MalformedPacket, TxtTooLong, + InvalidChar, }; // spdlog formatting for DNSParseError diff --git a/src/dns_packet.cpp b/src/dns_packet.cpp index 46a3ae1..e2525e2 100644 --- a/src/dns_packet.cpp +++ b/src/dns_packet.cpp @@ -58,6 +58,8 @@ auto fmt::formatter::format(DNSParseError e, format_context &ctx) break; case DNSParseError::EtherHdrProtoErr: error = "ethernet header error"; + case DNSParseError::InvalidChar: + error = "invalid character detected in packet"; break; } return formatter::format(error, ctx); @@ -75,6 +77,15 @@ inline tl::expected AdvanceReader(std::span '~'; + } + + return result; +} + tl::expected ReadFromDNSNameFormat(std::span bytes, std::span::iterator &reader) { DnsName name_parsed; @@ -149,6 +160,9 @@ tl::expected ReadFromDNSNameFormat(std::span ParseResourceRecord(std::span(rdata_bytes, reader, tag_length)); + if (contains_unprintable_chars(std::string_view(r_data.tag))) [[unlikely]] + return tl::unexpected(DNSParseError::InvalidChar); + uint16_t value_len = rdata_bytes.end() - reader; // Max length of the value is not specified in the RFC, diff --git a/test/utils_test.cc b/test/utils_test.cc index 3081a33..feb67cd 100644 --- a/test/utils_test.cc +++ b/test/utils_test.cc @@ -240,10 +240,9 @@ TYPED_TEST(FixedNameTests, FixedNameConcatenation) { { // Test concatenation within bounds - auto hello_opt = FixedNameType::init("Hello"); - ASSERT_TRUE(hello_opt.has_value()); + auto hello = FixedNameType("Hello"); - auto combined_opt = *hello_opt + "World"; + auto combined_opt = hello + "World"; if (ArraySize >= 11) { // "HelloWorld" + null terminator fits within 11 chars ASSERT_TRUE(combined_opt.has_value()); EXPECT_EQ(combined_opt->len, 10); @@ -267,6 +266,39 @@ TYPED_TEST(FixedNameTests, FixedNameConcatenation) { } } +TYPED_TEST(FixedNameTests, FixedNameAppend) { + constexpr auto ArraySize = TestFixture::ArraySize; + using FixedNameType = FixedName; + + { + // Test append within bounds + auto hello = FixedNameType("Hello"); + + auto success = hello += "World"; + if (ArraySize >= 11) { // "HelloWorld" + null terminator fits within 11 chars + ASSERT_TRUE(success); + EXPECT_EQ(hello.len, 10); + EXPECT_EQ(std::string_view(hello), "HelloWorld"); + } else { + ASSERT_FALSE(success); + } + } + { + // Test append that exceeds buffer size by one character + std::string almost_full(ArraySize - 2, + 'a'); // leaving 1 space for 'b' and 1 for null terminator + auto almost_full_opt = FixedNameType::init(almost_full); + + ASSERT_TRUE(almost_full_opt.has_value()); + + bool success = *almost_full_opt += "b"; + ASSERT_TRUE(success); + + success = *almost_full_opt += "c"; + ASSERT_FALSE(success); + } +} + std::optional> GetRefResolverList(std::vector input) { std::vector to_ret; for (auto& resolver_str : input) { diff --git a/utils/encode_dns_char_string.h b/utils/encode_dns_char_string.h new file mode 100644 index 0000000..a751411 --- /dev/null +++ b/utils/encode_dns_char_string.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace _detail { +static constexpr size_t char_encoding_max_size = 4; + +struct CharEncoding { + char encoded[char_encoding_max_size]; + uint8_t len; + + operator std::string_view() const { + return std::string_view(encoded, len); + } +}; + +static constexpr char to_decimal_char(int n) { + return static_cast('0' + n); +} + +static constexpr std::array to_decimal(int n) { + return {to_decimal_char(n / 100), to_decimal_char((n / 10) % 10), to_decimal_char(n % 10)}; +} + +static constexpr std::array char_map = []() { + std::array map{}; + for (int i = 0; i < 256; ++i) { + if (i >= 32 && i <= 126 && i != '"' && i != '\\') { + map[i] = {{static_cast(i)}, 1}; + } else { + auto decimal = to_decimal(i); + map[i] = {{'\\', decimal[0], decimal[1], decimal[2]}, 4}; + } + } + map['"'] = {{'\\', '"'}, 2}; + map['\\'] = {{'\\', '\\'}, 2}; + return map; +}(); +} // namespace _detail + +template +static FixedName encode_dns_char_string(FixedName in, + bool add_quotes = false) { + FixedName out; + + if (add_quotes) + out += "\""; + + for (unsigned char c : std::string_view(in)) { + const auto& encoding = _detail::char_map[c]; + out += encoding; + } + + if (add_quotes) + out += "\""; + + return out; +} \ No newline at end of file diff --git a/utils/fixed_name.hpp b/utils/fixed_name.hpp index 8b2ebb7..f7e8bed 100644 --- a/utils/fixed_name.hpp +++ b/utils/fixed_name.hpp @@ -85,7 +85,7 @@ struct FixedName { * @return std::optional containing the concatenated result, or std::nullopt if the result exceeds the buffer size. */ std::optional operator+(std::string_view other) const { - if (len + other.size() + 1 > N) { // +1 for the null terminator + if (len + other.size() >= N) { // >= N because we need space for null terminato return std::nullopt; } @@ -98,6 +98,22 @@ struct FixedName { return result; } + /** + * @brief Appends a string_view to the current FixedName. + * + * @param other The string_view to append. + * @return bool True if the operation was successful, false otherwise. + */ + bool operator+=(std::string_view other) { + if (len + other.size() >= N) { // >= N because we need space for null terminator + return false; + } + std::copy(other.begin(), other.end(), buf.begin() + len); + len += other.size(); + buf[len] = '\0'; // Ensure null-termination + return true; + } + /** * @brief Initializes a FixedName from a string view. *