From f4ab19ed1caecec8c035addce6a8379545722b32 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 21 Nov 2019 23:44:53 +0100 Subject: [PATCH] First commit --- .gitignore | 2 + CMakeLists.txt | 26 +++ server/CMakeLists.txt | 9 + server/main.cpp | 66 ++++++ server/src/handler.cpp | 84 +++++++ server/src/handler.h | 23 ++ server/src/net.h | 261 ++++++++++++++++++++++ server/src/server.cpp | 321 +++++++++++++++++++++++++++ server/src/server.h | 73 ++++++ util/CMakeLists.txt | 5 + util/include/teadns/builder.h | 186 ++++++++++++++++ util/include/teadns/parser.h | 257 ++++++++++++++++++++++ util/include/teadns/types.h | 168 ++++++++++++++ util/src/builder.cpp | 122 ++++++++++ util/src/parser.cpp | 403 ++++++++++++++++++++++++++++++++++ util/src/types.cpp | 130 +++++++++++ 16 files changed, 2136 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 server/CMakeLists.txt create mode 100644 server/main.cpp create mode 100644 server/src/handler.cpp create mode 100644 server/src/handler.h create mode 100644 server/src/net.h create mode 100644 server/src/server.cpp create mode 100644 server/src/server.h create mode 100644 util/CMakeLists.txt create mode 100644 util/include/teadns/builder.h create mode 100644 util/include/teadns/parser.h create mode 100644 util/include/teadns/types.h create mode 100644 util/src/builder.cpp create mode 100644 util/src/parser.cpp create mode 100644 util/src/types.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3d11e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cmake-build-*/** +.idea/* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6b18af2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.15) +project(TeaWebDNS) + +set(CMAKE_CXX_STANDARD 17) + +#Setup the compiler (Cant be done within a function!) +if (MSVC) + set(CompilerFlags + CMAKE_C_FLAGS_DEBUG + CMAKE_C_FLAGS_MINSIZEREL + CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_RELWITHDEBINFO + ) + foreach(CompilerFlag ${CompilerFlags}) + string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") + endforeach() +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -static-libgcc -static-libstdc++") +endif() + +add_subdirectory(util) +add_subdirectory(server) \ No newline at end of file diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt new file mode 100644 index 0000000..94f69f1 --- /dev/null +++ b/server/CMakeLists.txt @@ -0,0 +1,9 @@ +project(TeaWebDNS) + +# Require libevent +find_package(Libevent REQUIRED) +include_directories(${LIBEVENT_INCLUDE_DIRS}) +message(${LIBEVENT_INCLUDE_DIRS}) + +add_executable(TeaWebDNS ./main.cpp src/server.cpp src/handler.cpp) +target_link_libraries(TeaWebDNS ${LIBEVENT_STATIC_LIBRARIES} pthread teadns__parser) \ No newline at end of file diff --git a/server/main.cpp b/server/main.cpp new file mode 100644 index 0000000..ce55b7f --- /dev/null +++ b/server/main.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include "src/server.h" +#include "src/handler.h" + +using namespace ts::dns; + +std::vector bindings(uint16_t port) { + std::vector result{}; + + { + sockaddr_in& any_v4{reinterpret_cast(result.emplace_back())}; + memset(&any_v4, 0, sizeof(sockaddr_in)); + + any_v4.sin_family = AF_INET; + any_v4.sin_port = htons(port); //htons(53); + any_v4.sin_addr.s_addr = (1UL << 24U) | 127U; + } + + return result; +} + +__asm__(".symver getrandom,getrandom@GLIBC_2.2.5"); +int main(int argc, char** argv) { + evthread_use_pthreads(); + + if(argc < 2) { + std::cerr << "Missing port. Default DNS port is 53\n"; + return 1; + } + + uint16_t port{0}; + try { + port = std::stoul(argv[1]); + } catch(std::exception& ex) { + std::cerr << "Failed to parse port\n"; + return 1; + } + std::string error{}; + + auto handler = std::make_shared(); + DNSServer server{handler}; + + if(!server.start(bindings(port), error)) { + for(auto& binding : server.bindings()) + std::cout << " - " << binding->error << "\n"; + std::cerr << "Failed to start server: " << error << "\n"; + return 1; + } + + std::string line; + while(true) { + std::getline(std::cin, line); + if(line.empty()) + continue; + + if(line == "end" || line == "stop") { + std::cout << "Stopping server\n"; + break; + } else { + std::cerr << "Unknown command \"" << line << "\"\n"; + } + } + return 0; +} \ No newline at end of file diff --git a/server/src/handler.cpp b/server/src/handler.cpp new file mode 100644 index 0000000..39978b3 --- /dev/null +++ b/server/src/handler.cpp @@ -0,0 +1,84 @@ +#include "./handler.h" +#include "./server.h" +#include "./net.h" + +#include +#include +#include + +using namespace ts::dns; +using namespace ts::dns::builder; + +void WebDNSHandler::handle_message(const std::shared_ptr& binding, const sockaddr_storage &address, void *buffer, size_t size) { + std::cout << "Received DNS request from " << net::to_string(address) << ":\n"; + DNSParser parser{0, nullptr, buffer, size}; + + std::string error; + if(!parser.parse(error)) { + std::cout << " Failed to parse request: " << error << "\n"; + return; + } + + std::cout << " Query type: " << (uint32_t) parser.header().query_type() << "\n"; + std::cout << " Queries (" << parser.queries().size() << "):\n"; + for(auto& query : parser.queries()) + std::cout << " " << query->qname() << " (" << rrclass::name(query->qclass()) << "::" << rrtype::name(query->qtype()) << ")\n"; + std::cout << " Sending response.\n"; + + { + DNSBuilder response{}; + + response.header().id(*(uint16_t*) buffer); + response.header().set_answer(true); + response.header().set_response_code(rcode::NOERROR); + response.header().set_query_type(parser.header().query_type()); + + for(auto& query : parser.queries()) { + auto& q = response.push_query(); + q.set_qclass(query->qclass()); + q.set_qname(query->qname()); + q.set_qtype(query->qtype()); + + if(query->qclass() == rrclass::IN && query->qtype() == rrtype::A) { + auto dn = query->qname(); + uint8_t resp[4]; + { + size_t index = 0; + size_t aindex = 0; + do { + auto found = dn.find('.', index); + auto length = index == -1 ? dn.length() - index : found - index; + + try { + resp[aindex] = std::stoul(dn.substr(index, length)); + } catch(std::exception& ex) { + break; + } + aindex++; + index = found; + } while(index++ && aindex < 4); + if(aindex != 4) + continue; + } + std::cout << " Adding answer "; + for(size_t index = 0; index < 4; index++) + std::cout << (uint32_t) resp[index] << (index == 3 ? "\n" : "."); + + auto& a = response.push_answer(query->qname()); + a.set_class(query->qclass()); + a.set_type(query->qtype()); + a.set_ttl(120); + a.builder().set_address(resp); + } + } + + char rbuffer[1024]; + auto len = response.build(rbuffer, 1024, error); + if(!len) { + std::cout << " Failed to build response: " << error << "\n"; + return; + } + binding->send(address, rbuffer, len); + return; + } +} diff --git a/server/src/handler.h b/server/src/handler.h new file mode 100644 index 0000000..aadd417 --- /dev/null +++ b/server/src/handler.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace ts::dns { + class DNSServerBinding; + + class DNSHandler { + public: + virtual void handle_message(const std::shared_ptr& /* binding */, const sockaddr_storage& /* address */, void* /* buffer */, size_t /* buffer size */) = 0; + + private: + + }; + + class WebDNSHandler : public DNSHandler { + public: + void handle_message(const std::shared_ptr&, const sockaddr_storage &storage, void *pVoid, size_t size) override; + + private: + }; +} \ No newline at end of file diff --git a/server/src/net.h b/server/src/net.h new file mode 100644 index 0000000..8e34145 --- /dev/null +++ b/server/src/net.h @@ -0,0 +1,261 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef WIN32 + #include + #include + #include + #include +#else + #include + #include + #include + #include +#endif + +namespace net { + inline std::string to_string(const in6_addr& address) { + char buffer[INET6_ADDRSTRLEN]; + if(!inet_ntop(AF_INET6, (void*) &address, buffer, INET6_ADDRSTRLEN)) return ""; + return std::string(buffer); + } + + inline std::string to_string(const in_addr& address) { + char buffer[INET_ADDRSTRLEN]; + if(!inet_ntop(AF_INET, (void*) &address, buffer, INET_ADDRSTRLEN)) return ""; + return std::string(buffer); + } + + inline uint16_t port(const sockaddr_storage& address) { + switch(address.ss_family) { + case AF_INET: + return htons(((sockaddr_in*) &address)->sin_port); + case AF_INET6: + return htons(((sockaddr_in6*) &address)->sin6_port); + default: + return 0; + } + } + + inline std::string to_string(const sockaddr_storage& address, bool port = true) { + switch(address.ss_family) { + case AF_INET: + return to_string(((sockaddr_in*) &address)->sin_addr) + (port ? ":" + std::to_string(htons(((sockaddr_in*) &address)->sin_port)) : ""); + case AF_INET6: + return to_string(((sockaddr_in6*) &address)->sin6_addr) + (port ? ":" + std::to_string(htons(((sockaddr_in6*) &address)->sin6_port)) : ""); + default: + return "unknown_type"; + } + } + + inline socklen_t address_size(const sockaddr_storage& address) { + switch (address.ss_family) { + case AF_INET: return sizeof(sockaddr_in); + case AF_INET6: return sizeof(sockaddr_in6); + default: return 0; + } + } + + inline bool address_equal(const sockaddr_storage& a, const sockaddr_storage& b) { + if(a.ss_family != b.ss_family) return false; + if(a.ss_family == AF_INET) return ((sockaddr_in*) &a)->sin_addr.s_addr == ((sockaddr_in*) &b)->sin_addr.s_addr; + else if(a.ss_family == AF_INET6) { +#ifdef WIN32 + return memcmp(((sockaddr_in6*) &a)->sin6_addr.u.Byte, ((sockaddr_in6*) &b)->sin6_addr.u.Byte, 16) == 0; +#else + return memcmp(((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr8, ((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr8, 16) == 0; +#endif + } + return false; + } + + inline bool address_equal_ranged(const sockaddr_storage& a, const sockaddr_storage& b, uint8_t range) { + if(a.ss_family != b.ss_family) return false; + if(a.ss_family == AF_INET) { + auto address_a = ((sockaddr_in*) &a)->sin_addr.s_addr; + auto address_b = ((sockaddr_in*) &b)->sin_addr.s_addr; + + if(range > 32) + range = 32; + + range = (uint8_t) (32 - range); + + address_a <<= range; + address_b <<= range; + + return address_a == address_b; + } else if(a.ss_family == AF_INET6) { +#ifdef WIN32 + throw std::runtime_error("not implemented"); + //FIXME: Implement me! +#elif defined(__x86_64__) && false + static_assert(sizeof(__int128) == 16); + auto address_a = (__int128) ((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr32; + auto address_b = (__int128) ((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr32; + + if(range > 128) + range = 128; + range = (uint8_t) (128 - range); + + address_a <<= range; + address_b <<= range; + + return address_a == address_b; +#else + static_assert(sizeof(uint64_t) == 8); + + if(range > 128) + range = 128; + range = (uint8_t) (128 - range); + + + auto address_ah = (uint64_t) (((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr8 + 0); + auto address_al = (uint64_t) (((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr8 + 8); + auto address_bh = (uint64_t) (((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr8 + 0); + auto address_bl = (uint64_t) (((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr8 + 8); + + if(range > 64) { + /* only lower counts */ + return (address_al << (range - 64)) == (address_bl << (range - 64)); + } else { + return address_al == address_bl &&(address_bh << (range - 64)) == (address_ah << (range - 64)); + } +#endif + } + return false; + } + + + inline bool is_ipv6(const std::string& str) { + sockaddr_in6 sa{}; + return inet_pton(AF_INET6, str.c_str(), &(sa.sin6_addr)) != 0; + } + + inline bool is_ipv4(const std::string& str) { + sockaddr_in sa{}; + return inet_pton(AF_INET, str.c_str(), &(sa.sin_addr)) != 0; + } + + inline bool is_anybind(sockaddr_storage& storage) { + if(storage.ss_family == AF_INET) { + auto data = (sockaddr_in*) &storage; + return data->sin_addr.s_addr == 0; + } else if(storage.ss_family == AF_INET6) { + auto data = (sockaddr_in6*) &storage; +#ifdef WIN32 + auto& blocks = data->sin6_addr.u.Word; + return + blocks[0] == 0 && + blocks[1] == 0 && + blocks[2] == 0 && + blocks[3] == 0 && + blocks[4] == 0 && + blocks[5] == 0 && + blocks[6] == 0 && + blocks[7] == 0; +#else + auto& blocks = data->sin6_addr.__in6_u.__u6_addr32; + return blocks[0] == 0 && blocks[1] == 0 && blocks[2] == 0 && blocks[3] == 0; +#endif + } + return false; + } + + inline bool resolve_address(const std::string& address, sockaddr_storage& result) { + if(is_ipv4(address)) { + sockaddr_in s{}; + s.sin_port = 0; + s.sin_family = AF_INET; + + auto record = gethostbyname(address.c_str()); + if(!record) + return false; + s.sin_addr.s_addr = ((in_addr*) record->h_addr)->s_addr; + + memcpy(&result, &s, sizeof(s)); + return true; + } else if(is_ipv6(address)) { + sockaddr_in6 s{}; + s.sin6_family = AF_INET6; + s.sin6_port = 0; + s.sin6_flowinfo = 0; + s.sin6_scope_id = 0; + +#ifdef WIN32 + auto record = gethostbyname(address.c_str()); +#else + auto record = gethostbyname2(address.c_str(), AF_INET6); +#endif + if(!record) return false; + s.sin6_addr = *(in6_addr*) record->h_addr; + + memcpy(&result, &s, sizeof(s)); + return true; + } else if(address == "[::]" || address == "::") { + sockaddr_in6 s{}; + s.sin6_family = AF_INET6; + s.sin6_port = 0; + s.sin6_flowinfo = 0; + s.sin6_scope_id = 0; + + memcpy(&s.sin6_addr, &in6addr_any, sizeof(in6_addr)); + memcpy(&result, &s, sizeof(s)); + return true; + } + + return false; + } + + namespace helpers { + inline void strip(std::string& message) { + while(!message.empty()) { + if(message[0] == ' ') + message = message.substr(1); + else if(message[message.length() - 1] == ' ') + message = message.substr(0, message.length() - 1); + else break; + } + } + + inline std::deque split(const std::string& message, char delimiter) { + std::deque result{}; + size_t found, index = 0; + do { + found = message.find(delimiter, index); + result.push_back(message.substr(index, found - index)); + index = found + 1; + } while(index != 0); + return result; + } + } + + inline std::vector> resolve_bindings(const std::string& bindings, uint16_t port) { + auto binding_list = helpers::split(bindings, ','); + std::vector> result; + result.reserve(binding_list.size()); + + for(auto& address : binding_list) { + helpers::strip(address); + + sockaddr_storage element{}; + memset(&element, 0, sizeof(element)); + if(!resolve_address(address, element)) { + result.emplace_back(address, element, "address resolve failed"); + continue; + } + + if(element.ss_family == AF_INET) { + ((sockaddr_in*) &element)->sin_port = htons(port); + } else if(element.ss_family == AF_INET6) { + ((sockaddr_in6*) &element)->sin6_port = htons(port); + } + result.emplace_back(address, element, ""); + } + return result; + } +} \ No newline at end of file diff --git a/server/src/server.cpp b/server/src/server.cpp new file mode 100644 index 0000000..b930200 --- /dev/null +++ b/server/src/server.cpp @@ -0,0 +1,321 @@ +#include "./server.h" +#include "./handler.h" +#include "./net.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace ts::dns; + +DNSServer::~DNSServer() { + this->stop(); +} + +bool DNSServer::start(const std::vector &bindings, std::string &error) { + size_t successful_binds{0}; + + std::lock_guard lock{this->bind_lock}; + if(this->started) { + error = "server already bound"; + return false; + } + + this->event_base = event_base_new(); + if(!this->event_base) { + error = "failed to spawn event base"; + goto error_exit; + } + + this->event_base_ticker = evtimer_new(this->event_base, [](auto, auto, void* server){ + static_cast(server)->event_cb_timer(); + }, this); + if(!this->event_base_ticker) { + error = "failed to spawn heartbeat event"; + goto error_exit; + } + + this->_bindings.resize(bindings.size()); + for(size_t index = 0; index < bindings.size(); index++) { + auto binding = this->_bindings[index] = std::make_shared(); + binding->self = binding; + binding->server = this; + memcpy(&binding->address, &bindings[index], sizeof(sockaddr_storage)); + + if(!this->bind(*binding, error)) + binding->error = error; + else + successful_binds++; + } + + if(!successful_binds) { + error = "failed to bind to any address"; + goto error_exit; + } + + { + timeval timeout{10, 0}; + event_add(this->event_base_ticker, &timeout); + } + + this->event_base_executor = std::thread(std::bind(&DNSServer::event_executor, this)); + this->started = true; + return true; + + error_exit: + if(this->event_base_ticker) { + event_del_block(this->event_base_ticker); + event_free(this->event_base_ticker); + this->event_base_ticker = nullptr; + } + + if(this->event_base) { + event_base_free(this->event_base); + this->event_base = nullptr; + } + return false; +} + +void DNSServer::stop() { + std::unique_lock lock{this->bind_lock}; + if(!this->started) + return; + + this->started = false; + assert(this->event_base); //Must be set else the started flag was invalid + assert(this->event_base_ticker); //Must be set else the started flag was invalid + + + for(auto& binding : this->bindings()) + this->unbind(*binding); + + event_base_loopexit(this->event_base, nullptr); + { + timeval timeout{0, 0}; + event_add(this->event_base_ticker, &timeout); + } + + lock.unlock(); + this->event_base_executor.join(); + lock.lock(); + + event_free(this->event_base_ticker); + this->event_base_ticker = nullptr; +} + +bool DNSServer::bind(DNSServerBinding &binding, std::string &error) { + binding.socket = socket(binding.address.ss_family, SOCK_DGRAM, 0); + if(binding.socket < 2) { + error = "failed to create socket"; + return false; + } + int enable{1}; + + if(binding.address.ss_family == AF_INET6) { + if(setsockopt(binding.socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enable, sizeof(enable)) < 0) { + error = "failed to enable packet info (v6) (" + std::to_string(errno) + "/" + strerror(errno) + ")"; + goto cleanup_exit; + } + if(setsockopt(binding.socket, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(enable)) < 0) { + error = "failed to enable ip v6 only (" + std::to_string(errno) + "/" + strerror(errno) + ")"; + goto cleanup_exit; + } + } else { + if(setsockopt(binding.socket, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable)) < 0) { + error = "failed to enable packet info (" + std::to_string(errno) + "/" + strerror(errno) + ")"; + goto cleanup_exit; + } + } + + if(::bind(binding.socket, (const sockaddr*) &binding.address, sizeof(binding.address)) < 0) { + error = "failed to bind: " + std::to_string(errno) + "/" + strerror(errno); + goto cleanup_exit; + } + + if(fcntl(binding.socket, F_SETFL, fcntl(binding.socket, F_GETFL, 0) | O_NONBLOCK) < 0) { + error = "failed to enable noblock"; + goto cleanup_exit; + } + +#ifdef WIN32 + u_long enabled = 0; + auto non_block_rs = ioctlsocket(binding.socket, FIONBIO, &enabled); + if (non_block_rs != NO_ERROR) { + error = "failed to enable nonblock"; + goto cleanup_exit; + } +#endif + + binding.read_event = event_new(this->event_base, binding.socket, EV_READ | EV_PERSIST, &DNSServer::event_cb_read, &binding); + if(!binding.read_event) { + error = "failed to create read event"; + goto cleanup_exit; + } + + binding.write_event = event_new(this->event_base, binding.socket, EV_WRITE, &DNSServer::event_cb_write, &binding); + if(!binding.read_event) { + error = "failed to create write event"; + goto cleanup_exit; + } + + event_add(binding.read_event, nullptr); + return true; + + cleanup_exit: + if(binding.read_event) { + event_del_noblock(binding.read_event); + event_free(binding.read_event); + binding.read_event = nullptr; + } + + if(binding.write_event) { + event_del_noblock(binding.write_event); + event_free(binding.write_event); + binding.write_event = nullptr; + } + return false; +} + +void DNSServer::unbind(DNSServerBinding &binding) { + std::lock_guard lock{binding.io_lock}; + + if(binding.read_event) { + event_del_block(binding.read_event); + event_free(binding.read_event); + binding.read_event = nullptr; + } + + if(binding.write_event) { + event_del_block(binding.write_event); + event_free(binding.write_event); + binding.write_event = nullptr; + } + + if(binding.socket) { + ::shutdown(binding.socket, SHUT_RDWR); + ::close(binding.socket); + + binding.socket = 0; + } + + { + auto head = binding.write_buffer_head; + while(head) { + auto tmp = head; + head = head->next; + free(tmp); + } + + binding.write_buffer_head = nullptr; + binding.write_buffer_tail = nullptr; + } + + binding.self = nullptr; +} + +void DNSServerBinding::send(const sockaddr_storage &address, const void *payload, size_t size) { + std::lock_guard lock{this->io_lock}; + if(!this->write_event) + return; + + auto buffer = (char*) malloc(sizeof(BindingBuffer) + size); + auto bbuffer = (BindingBuffer*) buffer; + bbuffer->next = nullptr; + bbuffer->size = size; + memcpy(&bbuffer->target, &address, sizeof(address)); + memcpy(buffer + sizeof(BindingBuffer), payload, size); + + if(this->write_buffer_tail) { + assert(!this->write_buffer_tail->next); + this->write_buffer_tail->next = bbuffer; + this->write_buffer_tail = bbuffer; + } else { + assert(!this->write_buffer_head); + this->write_buffer_head = bbuffer; + this->write_buffer_tail = bbuffer; + } + + event_add(this->write_event, nullptr); +} + +void DNSServer::event_executor() { + do { + event_base_loop(this->event_base, EVLOOP_NO_EXIT_ON_EMPTY); + + std::lock_guard lock{this->bind_lock}; + if(!this->started) + return; + } while(true); +} + +void DNSServer::event_cb_timer() { + std::lock_guard lock{this->bind_lock}; + if(this->started) { + timeval timeout{10, 0}; + event_add(this->event_base_ticker, &timeout); + } +} + +void DNSServer::event_cb_read(evutil_socket_t fd, short, void *ptr_binding) { + auto binding = static_cast(ptr_binding); + auto binding_ref = binding->self; + if(!binding_ref) return; + + sockaddr_storage source_address{}; + socklen_t source_address_length{0}; + + ssize_t read_length{-1}; + size_t buffer_length = 1600; /* IPv6 MTU is ~1.5k */ + char buffer[1600]; + + size_t read_count = 0; + while(true) { //TODO: Some kind of timeout + source_address_length = sizeof(sockaddr_storage); + read_length = recvfrom(fd, (char*) buffer, buffer_length, MSG_DONTWAIT, (struct sockaddr*) &source_address, &source_address_length); + if(read_length <= 0) { + if(errno == EAGAIN) + break; + + std::cerr << "Failed to receive data: " << errno << "/" << strerror(errno) << "\n"; + break; /* this should never happen! */ + } + + read_count++; + //buffer, (size_t) read_length + + auto handler = binding->server->handler; + if(handler) + handler->handle_message(binding_ref, source_address, buffer, read_length); + else + std::cerr << "Dropping " << read_length << " bytes from " << net::to_string(source_address, true) << " because we've no handler\n"; + } +} + +void DNSServer::event_cb_write(evutil_socket_t fd, short, void *ptr_binding) { + auto binding = static_cast(ptr_binding); + auto binding_ref = binding->self; + if(!binding_ref) return; + + ssize_t code; + DNSServerBinding::BindingBuffer* buffer{nullptr}; + while(true) { + { + std::lock_guard lock{binding->io_lock}; + buffer = binding->write_buffer_head; + if(!buffer) + break; + + if(!(binding->write_buffer_head = binding->write_buffer_head->next)) + binding->write_buffer_tail = nullptr; + } + + code = sendto(fd, (char*) buffer + sizeof(DNSServerBinding::BindingBuffer), buffer->size, 0, (sockaddr*) &buffer->target, sizeof(buffer->target)); + if(code <= 0) + std::cerr << "Failed to send DNS response to " << net::to_string(buffer->target, true) << ": " << errno << "/" << strerror(errno); + free(buffer); + } +} \ No newline at end of file diff --git a/server/src/server.h b/server/src/server.h new file mode 100644 index 0000000..42bb140 --- /dev/null +++ b/server/src/server.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ts::dns { + class DNSServer; + class DNSServerBinding { + friend class DNSServer; + private: + struct BindingBuffer { + BindingBuffer* next{nullptr}; + sockaddr_storage target{}; + size_t size{0}; + }; + + public: + DNSServer* server{nullptr}; + std::shared_ptr self{nullptr}; + + sockaddr_storage address{}; + int socket{0}; + std::string error{}; + + inline bool active() const { return this->socket != 0; } + + std::mutex io_lock{}; + + event* read_event{nullptr}; + event* write_event{nullptr}; + + BindingBuffer* write_buffer_head{nullptr}; + BindingBuffer* write_buffer_tail{nullptr}; + + void send(const sockaddr_storage& /* target */, const void* /* buffer */, size_t /* length */); + }; + + class DNSHandler; + class DNSServer { + public: + DNSServer(std::shared_ptr handler) : handler{std::move(handler)} {}; + virtual ~DNSServer(); + + bool start(const std::vector& /* bindings */, std::string& /* error */); + void stop(); + + [[nodiscard]] const std::vector>& bindings() const { return this->_bindings; } + private: + std::shared_ptr handler; + + std::mutex bind_lock{}; + std::vector> _bindings; + bool started{false}; + + std::thread event_base_executor{}; + struct event* event_base_ticker{nullptr}; + struct event_base* event_base{nullptr}; + + bool bind(DNSServerBinding& /* binding */, std::string& /* error */); + void unbind(DNSServerBinding& /* binding */); + + void event_cb_timer(); + static void event_cb_read(evutil_socket_t fd, short, void *binding); + static void event_cb_write(evutil_socket_t fd, short, void *binding); + void event_executor(); + }; +} \ No newline at end of file diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..793cccf --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,5 @@ +project(TeaDNS-Parser) + +add_library(teadns__parser INTERFACE) +target_sources(teadns__parser INTERFACE src/parser.cpp src/types.cpp src/builder.cpp) +target_include_directories(teadns__parser INTERFACE include) \ No newline at end of file diff --git a/util/include/teadns/builder.h b/util/include/teadns/builder.h new file mode 100644 index 0000000..f4b426b --- /dev/null +++ b/util/include/teadns/builder.h @@ -0,0 +1,186 @@ +#pragma once + +#include +#include +#include +#include +#include "types.h" + +namespace ts::dns { + class DNSBuilder; + namespace builder { + namespace rrbuilder { + class base; + } + + class DNSHeader { + friend class ts::dns::DNSBuilder; + + template + constexpr static size_t be2leIndex() { + int result = index; + if(result >= 8) + result -= 8; + else + result += 8; + return result; + } + public: + DNSHeader() { + this->_buffer = (uint16_t*) &this->internal_buffer; + } + + explicit DNSHeader(uint16_t* buffer) : _buffer{buffer} { } + + inline void id(uint16_t id) { this->_buffer[0] = id; } + + inline void set_answer(bool flag) { this->set_option_bits<15>(flag, 1); } + + inline void set_authoritative_answer(bool flag) { this->set_option_bits<10>(flag, 1); } + inline void set_truncation(bool flag) { this->set_option_bits<9>(flag, 1); } + inline void set_recursion_desired(bool flag) { this->set_option_bits<8>(flag, 1); } + inline void set_recursion_available(bool flag) { this->set_option_bits<7>(flag, 1); } + + inline void set_query_type(uint8_t type) { this->set_option_bits<11>(type, 4); } + inline void set_response_code(uint8_t code) { this->set_option_bits<0>(code, 4); } + + inline void set_query_count(uint16_t count) { this->_buffer[2] = htons(count); } + inline void set_answer_count(uint16_t count) { this->_buffer[3] = htons(count); } + inline void set_authority_count(uint16_t count) { this->_buffer[4] = htons(count); } + inline void set_additional_count(uint16_t count) { this->_buffer[5] = htons(count); } + + [[nodiscard]] inline const void* buffer() const { return this->_buffer; } + [[nodiscard]] constexpr size_t buffer_size() const { return 12; } + private: + template ()> + inline void set_option_bits(uint8_t value, uint8_t bit_count) { + auto clear = (1UL << bit_count) - 1; + this->_buffer[1] &= ~(clear << le_index); + this->_buffer[1] |= ((value & clear) << le_index); + } + + uint16_t* _buffer{}; + uint16_t internal_buffer[6]{0,0,0,0,0,0}; + }; + + class DNSQuery { + friend class ts::dns::DNSBuilder; + public: + inline void set_qname(const std::string& value) { this->name = value; } + inline void set_qtype(const rrtype::value& value) { this->type = value; } + inline void set_qclass(const rrclass::value& value) { this->klass = value; } + + DNSQuery(std::string name, rrtype::value type, rrclass::value klass) : name{std::move(name)}, type{type}, klass{klass} {} + DNSQuery() = default; + private: + + std::string name; + rrtype::value type{rrtype::A}; + rrclass::value klass{rrclass::IN}; + }; + + class DNSResourceRecords { + friend class ts::dns::DNSBuilder; + public: + [[nodiscard]] inline std::string get_name() { return this->name; } + + inline void set_ttl(uint32_t value) { this->ttl = value; } + inline void set_type(rrtype::value value) { this->type = value; } + inline void set_class(rrclass::value value) { this->klass = value; } + + bool build(char*& /* buffer */, size_t& /* max length */, std::string& /* error */); + + template + [[nodiscard]] inline T& builder() { + if(T::type != this->type) + throw std::logic_error{"type mismatch"}; + if(!this->payload_builder) + this->payload_builder.reset(new T{this}); + auto entry = dynamic_cast(&*this->payload_builder); + if(!entry) + throw std::logic_error{"type mismatch to previous initialized"}; + return *entry; + } + private: + uint32_t ttl{0}; + + std::string name; + rrtype::value type{rrtype::A}; + rrclass::value klass{rrclass::IN}; + + std::unique_ptr payload_builder; + }; + + namespace rrbuilder { + class base { + public: + virtual bool build(char*& /* buffer */, size_t& /* max length */, std::string& /* error */) = 0; + + protected: + explicit base(DNSResourceRecords* handle) : handle{handle} {} + private: + DNSResourceRecords* handle; + }; + + #define define_builder(name, base, ...) \ + struct name : public base { \ + friend class builder::DNSResourceRecords; \ + public: \ + static constexpr auto type = rrtype::name; \ + bool build(char*&, size_t&, std::string&) override; \ + __VA_ARGS__ \ + private: \ + explicit name(DNSResourceRecords* handle) : base{handle} {} \ + } + + define_builder(A, base, + void set_address(uint32_t address) { this->address = address; } + inline void set_address(const uint8_t (&address)[4]) { + this->set_address((address[3] << 24UL) | (address[2] << 16UL) | (address[1] << 8UL) | address[0]); + } + + private: + uint32_t address{0}; + ); + + #undef define_builder + } + } + + class DNSBuilder { + public: + builder::DNSHeader& header() { return this->_header; } + + builder::DNSQuery& push_query() { + this->_queries.emplace_back(); + return this->_queries.back(); + } + + builder::DNSQuery& query(size_t index) { + if(this->_queries.size() <= index) + std::__throw_out_of_range("index out of range"); + + return this->_queries[index]; + } + + builder::DNSResourceRecords& push_answer(const std::string& domain) { + auto& entry = this->_answers.emplace_back(); + entry.name = domain; + return this->_answers.back(); + } + + builder::DNSResourceRecords& answer(size_t index) { + if(this->_answers.size() <= index) + std::__throw_out_of_range("index out of range"); + + return this->_answers[index]; + } + + size_t build(char* /* buffer */, size_t /* max length */, std::string& /* error */); + private: + builder::DNSHeader _header{}; + + std::vector _queries{}; + std::vector _answers{}; + }; +} \ No newline at end of file diff --git a/util/include/teadns/parser.h b/util/include/teadns/parser.h new file mode 100644 index 0000000..5901c20 --- /dev/null +++ b/util/include/teadns/parser.h @@ -0,0 +1,257 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef WIN32 + #include + #include + #include +#else + #include +#endif + +#include "./types.h" + +struct in6_addr; +namespace ts::dns { + namespace parser { + class DNSHeader; + class DNSQuery; + class DNSResourceRecords; + } + + class DNSParser; + struct DNSParseData; + + struct DNSParseData { +#ifdef WIN32 + bool wide_string{false}; + DNS_QUERY_RESULT data{}; +#else + uint8_t* buffer{nullptr}; + size_t length{0}; + + std::string parse_dns_dn(std::string& /* error */, size_t& /* index */, bool /* compression allowed */); +#endif + + ~DNSParseData(); + }; + + class DNSParser { + friend class Resolver; + public: + typedef std::vector> rr_list_t; + typedef std::vector> q_list_t; + +#ifdef WIN32 + DNSResponse(std::shared_ptr); +#else + DNSParser(uint8_t /* secure state */, const char* /* bogus */, void* /* packet */, size_t /* length */); +#endif + DNSParser(const DNSParser&) = delete; + DNSParser(DNSParser&&) = delete; + + bool parse(std::string& /* error */); +#ifndef WIN32 + [[nodiscard]] inline const std::string why_bogus() const { return this->bogus; } + + [[nodiscard]] inline const uint8_t* packet_data() const { return this->data->buffer; } + [[nodiscard]] inline size_t packet_length() const { return this->data->length; } + + [[nodiscard]] inline bool is_secure() const { return this->secure_state > 0; } + [[nodiscard]] inline bool is_secure_dnssec() const { return this->secure_state == 2; } + + [[nodiscard]] parser::DNSHeader header() const; +#endif + [[nodiscard]] q_list_t queries() const { return this->parsed_queries; } + [[nodiscard]] rr_list_t answers() const { return this->parsed_answers; } + [[nodiscard]] rr_list_t authorities() const { return this->parsed_authorities; } + [[nodiscard]] rr_list_t additionals() const { return this->parsed_additionals; } + private: +#ifndef WIN32 + std::shared_ptr parse_rr(std::string& /* error */, size_t& index, bool /* compression allowed dn */); + + std::string bogus; + uint8_t secure_state{0}; +#endif + std::shared_ptr data{nullptr}; + + bool is_parsed{false}; + std::string parse_error{}; + + q_list_t parsed_queries; + rr_list_t parsed_answers; + rr_list_t parsed_authorities; + rr_list_t parsed_additionals; + }; + + namespace parser { + #ifndef WIN32 + class DNSHeader { + friend class ts::dns::DNSParser; + public: + [[nodiscard]] inline size_t id() const { return this->field(0); } + + [[nodiscard]] inline bool is_answer() const { return this->field(1) & 0x1UL; } + [[nodiscard]] inline bool is_authoritative_answer() const { return (uint8_t) ((this->field(1) >> 5UL) & 0x01UL); } + [[nodiscard]] inline bool is_truncation() const { return (uint8_t) ((this->field(1) >> 6UL) & 0x01UL); } + [[nodiscard]] inline bool is_recursion_desired() const { return (uint8_t) ((this->field(1) >> 7UL) & 0x01UL); } + [[nodiscard]] inline bool is_recursion_available() const { return (uint8_t) ((this->field(1) >> 8UL) & 0x01UL); } + + [[nodiscard]] inline uint8_t query_type() const { return (uint8_t) ((this->field(1) >> 1UL) & 0x07UL); } + [[nodiscard]] inline uint8_t response_code() const { return (uint8_t) ((this->field(1) >> 12UL) & 0x07UL); } + + [[nodiscard]] inline uint16_t query_count() const { return ntohs(this->field(2)); } + [[nodiscard]] inline uint16_t answer_count() const { return ntohs(this->field(3)); } + [[nodiscard]] inline uint16_t authority_count() const { return ntohs(this->field(4)); } + [[nodiscard]] inline uint16_t additional_count() const { return htons(this->field(5)); } + private: + [[nodiscard]] uint16_t field(int index) const; + + explicit DNSHeader(const DNSParser* response) : response{response} {} + const DNSParser* response{nullptr}; + }; + #endif + + class DNSQuery { + friend class ts::dns::DNSParser; + public: + [[nodiscard]] inline std::string qname() const { return this->name; } + [[nodiscard]] inline rrtype::value qtype() const { return this->type; } + [[nodiscard]] inline rrclass::value qclass() const { return this->klass; } + private: + DNSQuery(std::string name, rrtype::value type, rrclass::value klass) : name{std::move(name)}, type{type}, klass{klass} {} + + std::string name; + rrtype::value type; + rrclass::value klass; + }; + + class DNSResourceRecords { + friend class ts::dns::DNSParser; + public: + [[nodiscard]] inline std::string qname() const { +#ifdef WIN32 + return std::string{this->nrecord->pName}; +#else + return this->name; +#endif + } + [[nodiscard]] inline rrtype::value atype() const { +#ifdef WIN32 + return static_cast(this->nrecord->wType); +#else + return this->type; +#endif + } + [[nodiscard]] inline rrclass::value aclass() const { +#ifdef WIN32 + return static_cast(1); +#else + return this->klass; +#endif + } + [[nodiscard]] inline uint16_t attl() const { +#ifdef WIN32 + return (uint16_t) this->nrecord->dwTtl; +#else + return this->ttl; +#endif + } + + #ifndef WIN32 + [[nodiscard]] const uint8_t* payload_data() const; + [[nodiscard]] inline size_t payload_length() const { return this->length; } + [[nodiscard]] inline size_t payload_offset() const { return this->offset; } + #else + [[nodiscard]] inline PDNS_RECORDA native_record() const { return this->nrecord; } + [[nodiscard]] bool is_wide_string() const; + #endif + + [[nodiscard]] inline std::shared_ptr dns_data() const { + return this->data; + } + + template + [[nodiscard]] inline T parse() const { + if(T::type != this->atype()) + throw std::logic_error{"util type mismatch"}; + return T{this}; + } + private: + std::shared_ptr data{nullptr}; + + #ifdef WIN32 + DNSResourceRecords(std::shared_ptr, PDNS_RECORDA); + + PDNS_RECORDA nrecord{nullptr}; + #else + DNSResourceRecords(std::shared_ptr, size_t, size_t, uint32_t, std::string , rrtype::value, rrclass::value); + + size_t offset{0}; + size_t length{0}; + + uint32_t ttl; + + std::string name; + rrtype::value type; + rrclass::value klass; + #endif + }; + + namespace rrparser { + struct base { + protected: + explicit base(const DNSResourceRecords* handle) : handle{handle} {} + const DNSResourceRecords* handle{nullptr}; + }; + + struct named_base : public base { + public: + [[nodiscard]] bool is_valid(); + [[nodiscard]] std::string name(); + protected: + explicit named_base(const DNSResourceRecords* handle) : base{handle} {} + }; + + #define define_parser(name, base, ...) \ + struct name : public base { \ + friend class parser::DNSResourceRecords; \ + public: \ + static constexpr auto type = rrtype::name; \ + __VA_ARGS__ \ + private: \ + explicit name(const DNSResourceRecords* handle) : base{handle} {} \ + } + + define_parser(A, base, + [[nodiscard]] bool is_valid(); + [[nodiscard]] std::string address_string(); + + [[nodiscard]] in_addr address(); + ); + + define_parser(AAAA, base, + [[nodiscard]] bool is_valid(); + [[nodiscard]] std::string address_string(); + [[nodiscard]] in6_addr address(); + ); + + define_parser(SRV, base, + [[nodiscard]] bool is_valid(); + + [[nodiscard]] uint16_t priority(); + [[nodiscard]] uint16_t weight(); + [[nodiscard]] uint16_t target_port(); + [[nodiscard]] std::string target_hostname(); + ); + + define_parser(CNAME, named_base); + + #undef define_parser + }; + } +} \ No newline at end of file diff --git a/util/include/teadns/types.h b/util/include/teadns/types.h new file mode 100644 index 0000000..8d45bc9 --- /dev/null +++ b/util/include/teadns/types.h @@ -0,0 +1,168 @@ +#pragma once + +#include +#include + +namespace ts::dns { + //https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2 + struct rrtype { + enum value : uint32_t { + A = 1, // a host address,[RFC1035], + NS = 2, // an authoritative name server,[RFC1035], + MD = 3, // a mail destination (OBSOLETE - use MX),[RFC1035], + MF = 4, // a mail forwarder (OBSOLETE - use MX),[RFC1035], + CNAME = 5, // the canonical name for an alias,[RFC1035], + SOA = 6, // marks the start of a zone of authority,[RFC1035], + MB = 7, // a mailbox domain name (EXPERIMENTAL),[RFC1035], + MG = 8, // a mail group member (EXPERIMENTAL),[RFC1035], + MR = 9, // a mail rename domain name (EXPERIMENTAL),[RFC1035], + NULL_ = 10, // a null RR (EXPERIMENTAL),[RFC1035], + WKS = 11, // a well known service description,[RFC1035], + PTR = 12, // a domain name pointer,[RFC1035], + HINFO = 13, // host information,[RFC1035], + MINFO = 14, // mailbox or mail list information,[RFC1035], + MX = 15, // mail exchange,[RFC1035], + TXT = 16, // text strings,[RFC1035], + RP = 17, // for Responsible Person,[RFC1183], + AFSDB = 18, // for AFS Data Base location,[RFC1183][RFC5864], + X25 = 19, // for X.25 PSDN address,[RFC1183], + ISDN = 20, // for ISDN address,[RFC1183], + RT = 21, // for Route Through,[RFC1183], + NSAP = 22, // "for NSAP address, NSAP style A record",[RFC1706], + NSAP_PTR = 23, // "for domain name pointer, NSAP style",[RFC1348][RFC1637][RFC1706], + SIG = 24, // for security signature,[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2931][RFC3110][RFC3008], + KEY = 25, // for security key,[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2539][RFC3008][RFC3110], + PX = 26, // X.400 mail mapping information,[RFC2163], + GPOS = 27, // Geographical Position,[RFC1712], + AAAA = 28, // IP6 Address,[RFC3596], + LOC = 29, // Location Information,[RFC1876], + NXT = 30, // Next Domain (OBSOLETE),[RFC3755][RFC2535], + EID = 31, // Endpoint Identifier,[Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt], + NIMLOC = 32, // Nimrod Locator,[1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt], + SRV = 33, // Server Selection,[1][RFC2782], + ATMA = 34, // ATM Address,"[ ATM Forum Technical Committee, ""ATM Name System, V2.0"", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.]", + NAPTR = 35, // Naming Authority Pointer,[RFC2915][RFC2168][RFC3403], + KX = 36, // Key Exchanger,[RFC2230], + CERT = 37, //CERT, // [RFC4398], + A6 = 38, // A6 (OBSOLETE - use AAAA),[RFC3226][RFC2874][RFC6563], + DNAME = 39, //DNAME, // [RFC6672], + SINK = 40, //SINK, // [Donald_E_Eastlake][http://tools.ietf.org/html/draft-eastlake-kitchen-sink], + OPT = 41, //OPT, // [RFC6891][RFC3225], + APL = 42, //APL, // [RFC3123], + DS = 43, // Delegation Signer,[RFC4034][RFC3658], + SSHFP = 44, // SSH Key Fingerprint,[RFC4255], + IPSECKEY = 45, //IPSECKEY, // [RFC4025], + RRSIG = 46, //RRSIG, // [RFC4034][RFC3755], + NSEC = 47, //NSEC, // [RFC4034][RFC3755], + DNSKEY = 48, //DNSKEY, // [RFC4034][RFC3755], + DHCID = 49, //DHCID, // [RFC4701], + NSEC3 = 50, //NSEC3, // [RFC5155], + NSEC3PARAM = 51, //NSEC3PARAM, // [RFC5155], + TLSA = 52, //TLSA, // [RFC6698], + SMIMEA = 53, // S/MIME cert association,[RFC8162],SMIMEA/smimea-completed-template + Unassigned = 54, // , + HIP = 55, // Host Identity Protocol,[RFC8005], + NINFO = 56, //NINFO [Jim_Reid], // NINFO/ninfo-completed-template + RKEY = 57, //RKEY [Jim_Reid], // RKEY/rkey-completed-template + TALINK = 58, // Trust Anchor LINK,[Wouter_Wijngaards],TALINK/talink-completed-template + CDS = 59, // Child DS,[RFC7344],CDS/cds-completed-template + CDNSKEY = 60, // DNSKEY(s) the Child wants reflected in DS,[RFC7344], + OPENPGPKEY = 61, // OpenPGP Key,[RFC7929],OPENPGPKEY/openpgpkey-completed-template + CSYNC = 62, // Child-To-Parent Synchronization,[RFC7477], + ZONEMD = 63, // message digest for DNS zone,[draft-wessels-dns-zone-digest],ZONEMD/zonemd-completed-template + //Unassigned = 64-98, + SPF = 99, // [RFC7208], + UINFO = 100, // [IANA-Reserved], + UID = 101, // [IANA-Reserved], + GID = 102, // [IANA-Reserved], + UNSPEC = 103, // [IANA-Reserved], + NID = 104, //[RFC6742], // ILNP/nid-completed-template + L32 = 105, //[RFC6742], // ILNP/l32-completed-template + L64 = 106, //[RFC6742], // ILNP/l64-completed-template + LP = 107, //[RFC6742], // ILNP/lp-completed-template + EUI48 = 108, // an EUI-48 address,[RFC7043],EUI48/eui48-completed-template + EUI64 = 109, // an EUI-64 address,[RFC7043],EUI64/eui64-completed-template + //Unassigned = 110-248, // , + TKEY = 249, // Transaction Key,[RFC2930], + TSIG = 250, // Transaction Signature,[RFC2845], + IXFR = 251, // incremental transfer,[RFC1995], + AXFR = 252, // transfer of an entire zone,[RFC1035][RFC5936], + MAILB = 253, // "mailbox-related RRs (MB, MG or MR)",[RFC1035], + MAILA = 254, // mail agent RRs (OBSOLETE - see MX),[RFC1035], + ANY = 255, // A request for some or all records the server has available,[RFC1035][RFC6895][RFC8482], + URI = 256, //URI [RFC7553], // URI/uri-completed-template + CAA = 257, // Certification Authority Restriction,[RFC-ietf-lamps-rfc6844bis-07],CAA/caa-completed-template + AVC = 258, // Application Visibility and Control,[Wolfgang_Riedel],AVC/avc-completed-template + DOA = 259, // Digital Object Architecture,[draft-durand-doa-over-dns],DOA/doa-completed-template + AMTRELAY = 260, // Automatic Multicast Tunneling Relay,[draft-ietf-mboned-driad-amt-discovery],AMTRELAY/amtrelay-completed-template + //Unassigned = 261-32767, + TA = 32768, // DNSSEC Trust Authorities,"[Sam_Weiler][http://cameo.library.cmu.edu/][ Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, + // Information Networking Institute, Carnegie Mellon University, April 2004.]", + DLV = 32769, // DNSSEC Lookaside Validation,[RFC4431], + //Unassigned = 32770-65279,, // , + //Private use,65280-65534,,,, + Reserved = 65535, + }; + + static std::map names; + + static const char* name(const value& value) { + if(names.count(value) > 0) + return names.at(value); + return "unknown"; + } + }; + + struct rrclass { +#ifdef IN + #undef IN +#endif + + enum value : uint32_t { + IN = 1, /* Internet */ + CH = 3, /* Chaos */ + HS = 4, /* Hesiod */ + + QCLASS_NONE = 254, + QCLASS_ANY = 255, + }; + + static std::map names; + + static const char* name(const value& value) { + if(names.count(value) > 0) + return names.at(value); + return "unknown"; + } + }; + + struct rcode { + enum value : uint8_t { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMP = 4, + REFUSED = 5, + YXDOMAIN = 6, + XRRSET = 7, + NOTAUTH = 8, + NOTZONE = 9 + }; + + static std::map names; + static std::map descriptions; + + static const char* name(const value& value) { + if(names.count(value) > 0) + return names.at(value); + return "unknown"; + } + + static const char* description(const value& value) { + if(descriptions.count(value) > 0) + return descriptions.at(value); + return "unknown"; + } + }; +} \ No newline at end of file diff --git a/util/src/builder.cpp b/util/src/builder.cpp new file mode 100644 index 0000000..a197de2 --- /dev/null +++ b/util/src/builder.cpp @@ -0,0 +1,122 @@ +#include +#include "teadns/builder.h" + +using namespace ts::dns; +using namespace ts::dns::builder; + +inline bool write_dn(char *&buffer, size_t& max_size, std::string dn) { + size_t index{0}; + size_t length; + do { + auto next = dn.find('.', index); + + length = next == -1 ? dn.length() - index : next - index; + if(max_size + 1 < length) + return false; + + *buffer = length; + memcpy(buffer + 1, dn.data() + index, length); + buffer += 1 + length; + max_size -= 1 + length; + + index = next; + } while(++index); + + if(max_size < 1) + return false; + *buffer = 0; + buffer++; + max_size--; + return true; +} + +size_t DNSBuilder::build(char *buffer, size_t max_size, std::string &error) { + size_t begin = max_size; + + if(max_size < this->_header.buffer_size()) { + error = "buffer too short for header"; + return 0; + } + + this->_header.set_query_count(this->_queries.size()); + this->_header.set_answer_count(this->_answers.size()); + this->_header.set_authority_count(0); + this->_header.set_additional_count(0); + + memcpy(buffer, this->_header.buffer(), this->_header.buffer_size()); + max_size -= this->_header.buffer_size(); + buffer += this->_header.buffer_size(); + + for(auto& query : this->_queries) { + if(!write_dn(buffer, max_size, query.name)) { + error = "buffer too short for query dn"; + return 0; + } + + if(max_size < 4) { + error = "buffer too short for query data"; + return 0; + } + + *(uint16_t*) buffer = htons(query.type); + buffer += 2; + + *(uint16_t*) buffer = htons(query.klass); + buffer += 2; + max_size -= 4; + } + + for(auto& answer : this->_answers) { + if(!answer.build(buffer, max_size, error)) { + error = "failed to build answer: " + error; + return 0; + } + } + return begin - max_size; +} + +bool DNSResourceRecords::build(char *&buffer, size_t &max_size, std::string &error) { + if(!write_dn(buffer, max_size, this->name)) { + error = "failed to write dn"; + return false; + } + + if(max_size < 10) { + error = "buffer too small"; + return false; + } + *(uint16_t*) buffer = htons(this->type); + max_size -= 2; buffer += 2; + + *(uint16_t*) buffer = htons(this->klass); + max_size -= 2; buffer += 2; + + *(uint32_t*) buffer = htonl(this->ttl); + max_size -= 4; buffer += 4; + + auto& length = *(uint16_t*) buffer; + max_size -= 2; buffer += 2; + if(this->payload_builder) { + auto start = max_size; + if(!this->payload_builder->build(buffer, max_size, error)) { + error = "failed to write payload: " + error; + return false; + } + length = htons(start - max_size); + } else { + length = 0; + } + return true; +} + +bool rrbuilder::A::build(char *&buffer, size_t &max_size, std::string &error) { + if(max_size < 4) { + error = "buffer too small"; + return false; + } + + memcpy(buffer, &this->address, 4); + buffer += 4; + max_size -= 4; + return true; +} \ No newline at end of file diff --git a/util/src/parser.cpp b/util/src/parser.cpp new file mode 100644 index 0000000..c1362a9 --- /dev/null +++ b/util/src/parser.cpp @@ -0,0 +1,403 @@ +#include "teadns/parser.h" + +#include +#include +#include + +#ifdef WIN32 + #include + #include + #include + #include + #include +#else + #include + #include +#include + +#endif + +using namespace ts::dns; +using namespace ts::dns::parser; +using namespace ts::dns::parser::rrparser; + +thread_local std::vector visited_links; +std::string DNSParseData::parse_dns_dn(std::string &error, size_t &index, bool allow_compression) { + if(allow_compression) { + visited_links.clear(); + visited_links.reserve(8); + + if(std::find(visited_links.begin(), visited_links.end(), index) != visited_links.end()) { + error = "circular link detected"; + return ""; + } + visited_links.push_back(index); + } + + error.clear(); + + std::string result; + result.reserve(256); //Max length is 253 + + while(true) { + if(index + 1 > this->length) { + error = "truncated data (missing code)"; + goto exit; + } + + auto code = this->buffer[index++]; + if(code == 0) break; + + if((code >> 6U) == 3) { + if(!allow_compression) { + error = "found link, but links are not allowed"; + goto exit; + } + + auto lower_addr = this->buffer[index++]; + if(index + 1 > this->length) { + error = "truncated data (missing lower link address)"; + goto exit; + } + + size_t addr = ((code & 0x3FU) << 8U) | lower_addr; + if(addr >= this->length) { + error = "invalid link address"; + goto exit; + } + auto tail = this->parse_dns_dn(error, addr, true); + if(!error.empty()) + goto exit; + + if(!result.empty()) + result += "." + tail; + else + result = tail; + break; + } else { + if(code > 63) { + error = "max domain label length is 63 characters"; + goto exit; + } + + if(!result.empty()) + result += "."; + + if(index + code >= this->length) { + error = "truncated data (domain label)"; + goto exit; + } + + result.append((const char*) (this->buffer + index), code); + index += code; + } + } + + exit: + if(allow_compression) visited_links.pop_back(); + return result; +} + +DNSParseData::~DNSParseData() { + ::free(this->buffer); +} + + +DNSParser::DNSParser(uint8_t secure_state, const char* bogus, void *packet, size_t size) { + this->bogus = bogus ? std::string{bogus} : std::string{"packet is secure"}; + this->secure_state = secure_state; + + this->data = std::make_shared(); + this->data->buffer = (uint8_t*) malloc(size); + this->data->length = size; + + memcpy(this->data->buffer, packet, size); +} + +parser::DNSHeader DNSParser::header() const { + return parser::DNSHeader{this}; +} + +bool DNSParser::parse(std::string &error) { + if(this->is_parsed) { + error = this->parse_error; + return error.empty(); + } + error.clear(); + this->is_parsed = true; + + auto header = this->header(); + size_t index = 12; /* 12 bits for the header */ + + { + auto count = header.query_count(); + this->parsed_queries.reserve(count); + + for(size_t idx = 0; idx < count; idx++) { + auto dn = this->data->parse_dns_dn(error, index, true); + if(!error.empty()) { + error = "failed to parse query " + std::to_string(idx) + " dn: " + error; // NOLINT(performance-inefficient-string-concatenation) + goto error_exit; + } + + if(index + 4 > this->packet_length()) { + error = "truncated data for query " + std::to_string(index); + goto error_exit; + } + + auto type = (rrtype::value) ntohs(*(uint16_t*) (this->data->buffer + index)); + index += 2; + + auto klass = (rrclass::value) ntohs(*(uint16_t*) (this->data->buffer + index)); + index += 2; + + this->parsed_queries.emplace_back(new parser::DNSQuery{dn, type, klass}); + } + } + + { + auto count = header.answer_count(); + this->parsed_answers.reserve(count); + + for(size_t idx = 0; idx < count; idx++) { + this->parsed_answers.push_back(this->parse_rr(error, index, true)); + if(!error.empty()) { + error = "failed to parse answer " + std::to_string(idx) + ": " + error; // NOLINT(performance-inefficient-string-concatenation) + goto error_exit; + } + } + } + + { + auto count = header.authority_count(); + this->parsed_authorities.reserve(count); + + for(size_t idx = 0; idx < count; idx++) { + this->parsed_authorities.push_back(this->parse_rr(error, index, true)); + if(!error.empty()) { + error = "failed to parse authority " + std::to_string(idx) + ": " + error; // NOLINT(performance-inefficient-string-concatenation) + goto error_exit; + } + } + } + + { + auto count = header.additional_count(); + this->parsed_additionals.reserve(count); + + for(size_t idx = 0; idx < count; idx++) { + this->parsed_additionals.push_back(this->parse_rr(error, index, true)); + if(!error.empty()) { + error = "failed to parse additional " + std::to_string(idx) + ": " + error; // NOLINT(performance-inefficient-string-concatenation) + goto error_exit; + } + } + } + + return true; + + error_exit: + this->parsed_queries.clear(); + this->parsed_answers.clear(); + this->parsed_authorities.clear(); + this->parsed_additionals.clear(); + return false; +} + +std::shared_ptr DNSParser::parse_rr(std::string &error, size_t &index, bool allow_compressed) { + auto dn = this->data->parse_dns_dn(error, index, allow_compressed); + if(!error.empty()) { + error = "failed to parse rr dn: " + error; // NOLINT(performance-inefficient-string-concatenation) + return nullptr; + } + + if(index + 10 > this->packet_length()) { + error = "truncated header"; + return nullptr; + } + + auto type = (rrtype::value) ntohs(*(uint16_t*) (this->data->buffer + index)); + index += 2; + + auto klass = (rrclass::value) ntohs(*(uint16_t*) (this->data->buffer + index)); + index += 2; + + auto ttl = ntohl(*(uint32_t*) (this->data->buffer + index)); + index += 4; + + auto payload_length = ntohs(*(uint16_t*) (this->data->buffer + index)); + index += 2; + + if(index + payload_length > this->packet_length()) { + error = "truncated body"; + return nullptr; + } + + auto response = std::shared_ptr(new parser::DNSResourceRecords{this->data, index, payload_length, ttl, dn, type, klass}); + index += payload_length; + return response; +} + + +#ifndef WIN32 +uint16_t DNSHeader::field(int index) const { + return ((uint16_t*) this->response->packet_data())[index]; +} + +DNSResourceRecords::DNSResourceRecords(std::shared_ptr packet, size_t payload_offset, size_t length, uint32_t ttl, std::string name, rrtype::value type, rrclass::value klass) + : offset{payload_offset}, length{length}, ttl{ttl}, name{std::move(name)}, type{type}, klass{klass} { + this->data = std::move(packet); +} + +const uint8_t* DNSResourceRecords::payload_data() const { + return this->data->buffer + this->offset; +} +#else +DNSResourceRecords::DNSResourceRecords(std::shared_ptr data, PDNS_RECORDA rdata) : nrecord{rdata}, data{std::move(data)} { } +bool DNSResourceRecords::is_wide_string() const { + return this->data->wide_string; +} +#endif + +bool A::is_valid() { +#ifdef WIN32 + return true; +#else + return this->handle->payload_length() == 4; +#endif +} + +in_addr A::address() { +#ifdef WIN32 + in_addr result{}; + result.S_un.S_addr = this->handle->native_record()->Data.A.IpAddress; + return result; +#else + //TODO: Attention: Unaligned access + return {*(uint32_t*) this->handle->payload_data()}; +#endif +} + +std::string A::address_string() { +#ifdef WIN32 + struct in_addr address = this->address(); + char buffer[17]; + RtlIpv4AddressToStringA(&address, buffer); + return std::string{buffer}; +#else + auto _1 = this->handle->payload_data()[0], + _2 = this->handle->payload_data()[1], + _3 = this->handle->payload_data()[2], + _4 = this->handle->payload_data()[3]; + return std::to_string(_1) + "." + std::to_string(_2) + "." + std::to_string(_3) + "." + std::to_string(_4); +#endif +} + +//---------------- AAAA +#ifdef WIN32 +bool AAAA::is_valid() { + return true; +} + +std::string AAAA::address_string() { + struct in6_addr address = this->address(); + char buffer[47]; + RtlIpv6AddressToStringA(&address, buffer); //Supported for Win7 as well and not only above 8.1 like inet_ntop + return std::string{buffer}; +} + +in6_addr AAAA::address() { + in6_addr result{}; + memcpy(result.u.Byte, this->handle->native_record()->Data.AAAA.Ip6Address.IP6Byte, 16); + return result; +} +#else +bool AAAA::is_valid() { + return this->handle->payload_length() == 16; +} + +std::string AAAA::address_string() { + auto address = this->address(); + + char buffer[INET6_ADDRSTRLEN]; + if(!inet_ntop(AF_INET6, (void*) &address, buffer, INET6_ADDRSTRLEN)) return ""; + return std::string(buffer); +} + +in6_addr AAAA::address() { + return { + .__in6_u = { + .__u6_addr32 = { + //TODO: Attention unaligned memory access + ((uint32_t*) this->handle->payload_data())[0], + ((uint32_t*) this->handle->payload_data())[1], + ((uint32_t*) this->handle->payload_data())[2], + ((uint32_t*) this->handle->payload_data())[3] + } + } + }; +} +#endif + +//---------------- SRV +#ifdef WIN32 +bool SRV::is_valid() { return true; } +std::string SRV::target_hostname() { + if(this->handle->is_wide_string()) { + auto result = std::wstring{ ((PDNS_RECORDW) this->handle->native_record())->Data.Srv.pNameTarget }; + return std::string{result.begin(), result.end()}; + } else { + return std::string{ this->handle->native_record()->Data.Srv.pNameTarget }; + } +} +uint16_t SRV::priority() { return this->handle->native_record()->Data.SRV.wPriority; } +uint16_t SRV::weight() { return this->handle->native_record()->Data.SRV.wWeight; } +uint16_t SRV::target_port() { return this->handle->native_record()->Data.SRV.wPort; } +#else +bool SRV::is_valid() { + if(this->handle->payload_length() < 7) + return false; + size_t index = this->handle->payload_offset() + 6; + std::string error{}; + this->handle->dns_data()->parse_dns_dn(error, index, true); + return error.empty(); +} + +std::string SRV::target_hostname() { + size_t index = this->handle->payload_offset() + 6; + std::string error{}; + return this->handle->dns_data()->parse_dns_dn(error, index, true); +} + + +uint16_t SRV::priority() { return ntohs(((uint16_t*) this->handle->payload_data())[0]); } +uint16_t SRV::weight() { return ntohs(((uint16_t*) this->handle->payload_data())[1]); } +uint16_t SRV::target_port() { return ntohs(((uint16_t*) this->handle->payload_data())[2]); } +#endif + +//---------------- All types with a name +bool named_base::is_valid() { +#ifdef WIN32 + return true; +#else + size_t index = this->handle->payload_offset(); + std::string error{}; + this->handle->dns_data()->parse_dns_dn(error, index, true); + return error.empty(); +#endif +} + +std::string named_base::name() { +#ifdef WIN32 + if(this->handle->is_wide_string()) { + auto result = std::wstring{ ((PDNS_RECORDW) this->handle->native_record())->Data.Cname.pNameHost }; + return std::string{result.begin(), result.end()}; + } else { + return std::string{ this->handle->native_record()->Data.Cname.pNameHost }; + } +#else + size_t index = this->handle->payload_offset(); + std::string error{}; + return this->handle->dns_data()->parse_dns_dn(error, index, true); +#endif +} diff --git a/util/src/types.cpp b/util/src/types.cpp new file mode 100644 index 0000000..1bda301 --- /dev/null +++ b/util/src/types.cpp @@ -0,0 +1,130 @@ +#include "teadns/types.h" + +std::map ts::dns::rrtype::names{ + {ts::dns::rrtype::A, "A"}, + {ts::dns::rrtype::NS, "NS"}, + {ts::dns::rrtype::MD, "MD"}, + {ts::dns::rrtype::MF, "MF"}, + {ts::dns::rrtype::CNAME, "CNAME"}, + {ts::dns::rrtype::SOA, "SOA"}, + {ts::dns::rrtype::MB, "MB"}, + {ts::dns::rrtype::MG, "MG"}, + {ts::dns::rrtype::MR, "MR"}, + {ts::dns::rrtype::NULL_, "NULL_"}, + {ts::dns::rrtype::WKS, "WKS"}, + {ts::dns::rrtype::PTR, "PTR"}, + {ts::dns::rrtype::HINFO, "HINFO"}, + {ts::dns::rrtype::MINFO, "MINFO"}, + {ts::dns::rrtype::MX, "MX"}, + {ts::dns::rrtype::TXT, "TXT"}, + {ts::dns::rrtype::RP, "RP"}, + {ts::dns::rrtype::AFSDB, "AFSDB"}, + {ts::dns::rrtype::X25, "X25"}, + {ts::dns::rrtype::ISDN, "ISDN"}, + {ts::dns::rrtype::RT, "RT"}, + {ts::dns::rrtype::NSAP, "NSAP"}, + {ts::dns::rrtype::NSAP_PTR, "NSAP_PTR"}, + {ts::dns::rrtype::SIG, "SIG"}, + {ts::dns::rrtype::KEY, "KEY"}, + {ts::dns::rrtype::PX, "PX"}, + {ts::dns::rrtype::GPOS, "GPOS"}, + {ts::dns::rrtype::AAAA, "AAAA"}, + {ts::dns::rrtype::LOC, "LOC"}, + {ts::dns::rrtype::NXT, "NXT"}, + {ts::dns::rrtype::EID, "EID"}, + {ts::dns::rrtype::NIMLOC, "NIMLOC"}, + {ts::dns::rrtype::SRV, "SRV"}, + {ts::dns::rrtype::ATMA, "ATMA"}, + {ts::dns::rrtype::NAPTR, "NAPTR"}, + {ts::dns::rrtype::KX, "KX"}, + {ts::dns::rrtype::CERT, "CERT"}, + {ts::dns::rrtype::A6, "A6"}, + {ts::dns::rrtype::DNAME, "DNAME"}, + {ts::dns::rrtype::SINK, "SINK"}, + {ts::dns::rrtype::OPT, "OPT"}, + {ts::dns::rrtype::APL, "APL"}, + {ts::dns::rrtype::DS, "DS"}, + {ts::dns::rrtype::SSHFP, "SSHFP"}, + {ts::dns::rrtype::IPSECKEY, "IPSECKEY"}, + {ts::dns::rrtype::RRSIG, "RRSIG"}, + {ts::dns::rrtype::NSEC, "NSEC"}, + {ts::dns::rrtype::DNSKEY, "DNSKEY"}, + {ts::dns::rrtype::DHCID, "DHCID"}, + {ts::dns::rrtype::NSEC3, "NSEC3"}, + {ts::dns::rrtype::NSEC3PARAM, "NSEC3PARAM"}, + {ts::dns::rrtype::TLSA, "TLSA"}, + {ts::dns::rrtype::SMIMEA, "SMIMEA"}, + {ts::dns::rrtype::Unassigned, "Unassigned"}, + {ts::dns::rrtype::HIP, "HIP"}, + {ts::dns::rrtype::NINFO, "NINFO"}, + {ts::dns::rrtype::RKEY, "RKEY"}, + {ts::dns::rrtype::TALINK, "TALINK"}, + {ts::dns::rrtype::CDS, "CDS"}, + {ts::dns::rrtype::CDNSKEY, "CDNSKEY"}, + {ts::dns::rrtype::OPENPGPKEY, "OPENPGPKEY"}, + {ts::dns::rrtype::CSYNC, "CSYNC"}, + {ts::dns::rrtype::ZONEMD, "ZONEMD"}, + {ts::dns::rrtype::SPF, "SPF"}, + {ts::dns::rrtype::UINFO, "UINFO"}, + {ts::dns::rrtype::UID, "UID"}, + {ts::dns::rrtype::GID, "GID"}, + {ts::dns::rrtype::UNSPEC, "UNSPEC"}, + {ts::dns::rrtype::NID, "NID"}, + {ts::dns::rrtype::L32, "L32"}, + {ts::dns::rrtype::L64, "L64"}, + {ts::dns::rrtype::LP, "LP"}, + {ts::dns::rrtype::EUI48, "EUI48"}, + {ts::dns::rrtype::EUI64, "EUI64"}, + {ts::dns::rrtype::TKEY, "TKEY"}, + {ts::dns::rrtype::TSIG, "TSIG"}, + {ts::dns::rrtype::IXFR, "IXFR"}, + {ts::dns::rrtype::AXFR, "AXFR"}, + {ts::dns::rrtype::MAILB, "MAILB"}, + {ts::dns::rrtype::MAILA, "MAILA"}, + {ts::dns::rrtype::ANY, "ANY"}, + {ts::dns::rrtype::URI, "URI"}, + {ts::dns::rrtype::CAA, "CAA"}, + {ts::dns::rrtype::AVC, "AVC"}, + {ts::dns::rrtype::DOA, "DOA"}, + {ts::dns::rrtype::AMTRELAY, "AMTRELAY"}, + {ts::dns::rrtype::TA, "TA"}, + {ts::dns::rrtype::DLV, "DLV"}, + {ts::dns::rrtype::Reserved, "Reserved"}, +}; + + +std::map ts::dns::rrclass::names{ + {ts::dns::rrclass::IN, "IN"}, + {ts::dns::rrclass::CH, "CH"}, + {ts::dns::rrclass::HS, "HS"}, + {ts::dns::rrclass::QCLASS_ANY, "QCLASS_ANY"}, + {ts::dns::rrclass::QCLASS_NONE, "QCLASS_NONE"}, +}; + + +std::map ts::dns::rcode::names{ + {ts::dns::rcode::NOERROR, "NOERROR"}, + {ts::dns::rcode::FORMERR, "FORMERR"}, + {ts::dns::rcode::SERVFAIL, "SERVFAIL"}, + {ts::dns::rcode::NXDOMAIN, "NXDOMAIN"}, + {ts::dns::rcode::NOTIMP, "NOTIMP"}, + {ts::dns::rcode::REFUSED, "REFUSED"}, + {ts::dns::rcode::YXDOMAIN, "YXDOMAIN"}, + {ts::dns::rcode::XRRSET, "XRRSET"}, + {ts::dns::rcode::NOTAUTH, "NOTAUTH"}, + {ts::dns::rcode::NOTZONE, "NOTZONE"}, +}; + + +std::map ts::dns::rcode::descriptions{ + {ts::dns::rcode::NOERROR, "DNS Query completed successfully"}, + {ts::dns::rcode::FORMERR, "DNS Query Format Error"}, + {ts::dns::rcode::SERVFAIL, "Server failed to complete the DNS request"}, + {ts::dns::rcode::NXDOMAIN, "Domain name does not exist. "}, + {ts::dns::rcode::NOTIMP, "Function not implemented"}, + {ts::dns::rcode::REFUSED, "The server refused to answer for the query"}, + {ts::dns::rcode::YXDOMAIN, "Name that should not exist, does exist"}, + {ts::dns::rcode::XRRSET, "RRset that should not exist, does exist"}, + {ts::dns::rcode::NOTAUTH, "Server not authoritative for the zone"}, + {ts::dns::rcode::NOTZONE, "Name not in zone"}, +}; \ No newline at end of file