Skip to content
This repository has been archived by the owner on Mar 26, 2024. It is now read-only.

Implement cross-platform DNS resolver #2

Merged
merged 3 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ cmx_add_library(kstd-platform-static STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(kstd-platform-static PUBLIC "${CMAKE_SOURCE_DIR}/include")
cmx_include_kstd_core(kstd-platform-static PUBLIC)

if (CMX_PLATFORM_UNIX)
target_link_libraries(kstd-platform PUBLIC -lresolv)
target_link_libraries(kstd-platform-static PUBLIC -lresolv)
endif()

if (CMX_PLATFORM_LINUX)
target_link_libraries(kstd-platform PUBLIC -ldl -lrt)
target_link_libraries(kstd-platform-static PUBLIC -ldl -lrt)
endif ()

# Tests
if (KSTD_PLATFORM_BUILD_TESTS)
cmx_add_tests(kstd-platform-tests "${CMAKE_SOURCE_DIR}/test")
cmx_include_gtest(kstd-platform-tests PRIVATE)
target_link_libraries(kstd-platform-tests PRIVATE kstd-platform-static)
add_dependencies(kstd-platform-tests kstd-platform-static)
endif ()

if (CMX_PLATFORM_LINUX)
target_link_libraries(kstd-platform PUBLIC -ldl -lrt)
target_link_libraries(kstd-platform-static PUBLIC -ldl -lrt)
if (KSTD_PLATFORM_BUILD_TESTS)
target_link_libraries(kstd-platform-tests PRIVATE -ldl -lrt)
endif ()
endif ()
100 changes: 100 additions & 0 deletions include/kstd/platform/dns.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2023 Karma Krafts & associates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @author Cedric Hammes
* @since 12/08/2023
*/

#pragma once

#include "kstd/platform/platform.hpp"
#include <fmt/format.h>
#include <initializer_list>
#include <kstd/bitflags.hpp>
#include <kstd/result.hpp>
#include <regex>
#include <stdexcept>
#include <string>

#ifdef PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <WinDNS.h>
#include <Windows.h>
#include <kstd/option.hpp>
#else
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netinet/in.h>
#include <resolv.h>
#endif

namespace kstd::platform {

#if defined(PLATFORM_WINDOWS)
enum class RecordType : kstd::u16 {
A = DNS_TYPE_A,
AAAA = DNS_TYPE_AAAA
};

struct IP4Array {
DWORD address_count;
std::array<IP4_ADDRESS, 4> addresses;
};
#elif defined(PLATFORM_LINUX)
enum class RecordType : kstd::u16 {
A = T_A,
AAAA = T_AAAA
};
#else
enum class RecordType : kstd::u16 {
A = __ns_type::ns_t_a,
AAAA = __ns_type::ns_t_aaaa
};
#endif

class Resolver final {
#ifdef PLATFORM_WINDOWS
kstd::Option<IP4Array> _dns_addresses;
#else
std::vector<std::string> _dns_addresses;
#endif

public:
Resolver(std::vector<std::string> dns_addresses);
Resolver();
~Resolver();
KSTD_DEFAULT_MOVE(Resolver, Resolver)
KSTD_NO_COPY(Resolver, Resolver)

[[nodiscard]] auto resolve(const std::string& address, RecordType type) noexcept -> kstd::Result<std::string>;
};

inline auto is_ipv4_address(const std::string& address) noexcept -> bool {
static auto s_pattern = std::regex(R"(([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3}))");
return std::regex_match(address, s_pattern);
}

inline auto is_ipv6_address(const std::string& address) noexcept -> bool {
static auto s_pattern = std::regex(
"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-"
"F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){"
"1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}"
"|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,"
"4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25["
"0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]"
")\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))");
return std::regex_match(address, s_pattern);
}
}// namespace kstd::platform
85 changes: 85 additions & 0 deletions src/linux/dns.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2023 Karma Krafts & associates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @author Cedric Hammes
* @since 12/08/2023
*/

#ifdef PLATFORM_LINUX

#include "kstd/platform/dns.hpp"

namespace kstd::platform {
Resolver::Resolver(const std::vector<std::string> dns_addresses) :
_dns_addresses {dns_addresses} {
if(dns_addresses.size() == 0) {
throw std::runtime_error("Unable to initialize list of DNS servers: No DNS server specified");
}
}

Resolver::Resolver() :
_dns_addresses {} {
}

Resolver::~Resolver() {
}

auto Resolver::resolve(const std::string& address, const RecordType type) noexcept -> kstd::Result<std::string> {
if(address == "localhost") {
switch(type) {
case RecordType::A: return kstd::Result<std::string> {"127.0.0.1"};
case RecordType::AAAA: return kstd::Result<std::string> {"::1"};
}
}

// Init resolv API
res_init();

// Generate custom nameserver list if needed
if(_dns_addresses.size() > 0) {
_res.nscount = _dns_addresses.size();
for(int i = 0; i < _res.nscount; ++i) {
if(is_ipv4_address(_dns_addresses[i])) {
_res.nsaddr_list[i].sin_family = AF_INET;
}
else if(is_ipv6_address(_dns_addresses[i])) {
_res.nsaddr_list[i].sin_family = AF_INET6;
}
else {
return kstd::Error {fmt::format("Unable to resolve address of {}: Illegal DNS server address {}",
address, _dns_addresses[i])};
}
_res.nsaddr_list[i].sin_addr.s_addr = ::inet_addr(_dns_addresses[i].c_str());
_res.nsaddr_list[i].sin_port = htons(53);
}
}

// Send DNS request
std::array<u_char, 4096> response_buffer {'\0'};
kstd::isize response_length = ::res_query(address.c_str(), C_IN, static_cast<int>(type), response_buffer.data(),
sizeof(response_buffer));

if(response_length < 0) {
return kstd::Error {fmt::format("Unable to resolve address of {}: {}", address, get_last_error())};
}

if(response_length == 0) {
return kstd::Error {fmt::format("Unable to resolve address of {}: There is no response", address)};
}
return std::string {response_buffer.cbegin(), response_buffer.cbegin() + response_length + 1};
}
}// namespace kstd::platform

#endif
85 changes: 85 additions & 0 deletions src/macos/dns.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2023 Karma Krafts & associates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @author Cedric Hammes
* @since 12/08/2023
*/

#ifdef PLATFORM_APPLE

#include "kstd/platform/dns.hpp"

namespace kstd::platform {
Resolver::Resolver(const std::vector<std::string> dns_addresses) :
_dns_addresses {dns_addresses} {
if(dns_addresses.size() == 0) {
throw std::runtime_error("Unable to initialize list of DNS servers: No DNS server specified");
}
}

Resolver::Resolver() :
_dns_addresses {} {
}

Resolver::~Resolver() {
}

auto Resolver::resolve(const std::string& address, const RecordType type) noexcept -> kstd::Result<std::string> {
if(address == "localhost") {
switch(type) {
case RecordType::A: return kstd::Result<std::string> {"127.0.0.1"};
case RecordType::AAAA: return kstd::Result<std::string> {"::1"};
}
}

// Init resolv API
res_init();

// Generate custom nameserver list if needed
if(_dns_addresses.size() > 0) {
_res.nscount = _dns_addresses.size();
for(int i = 0; i < _res.nscount; ++i) {
if(is_ipv4_address(_dns_addresses[i])) {
_res.nsaddr_list[i].sin_family = AF_INET;
}
else if(is_ipv6_address(_dns_addresses[i])) {
_res.nsaddr_list[i].sin_family = AF_INET6;
}
else {
return kstd::Error {fmt::format("Unable to resolve address of {}: Illegal DNS server address {}",
address, _dns_addresses[i])};
}
_res.nsaddr_list[i].sin_addr.s_addr = ::inet_addr(_dns_addresses[i].c_str());
_res.nsaddr_list[i].sin_port = htons(53);
}
}

// Send DNS request
std::array<u_char, 4096> response_buffer {'\0'};
kstd::isize response_length = ::res_query(address.c_str(), __ns_class::ns_c_in, static_cast<int>(type),
response_buffer.data(), sizeof(response_buffer));

if(response_length < 0) {
return kstd::Error {fmt::format("Unable to resolve address {}: {}", address, get_last_error())};
}

if(response_length == 0) {
return kstd::Error {fmt::format("Unable to resolve address {}: There is no response", address)};
}
return std::string {response_buffer.cbegin(), response_buffer.cbegin() + response_length + 1};
}
}// namespace kstd::platform

#endif
Loading