diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 94f69f1..4308352 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -5,5 +5,7 @@ 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 +find_package(spdlog REQUIRED) + +add_executable(TeaWebDNS ./main.cpp src/server.cpp src/handler.cpp src/logger.cpp) +target_link_libraries(TeaWebDNS ${LIBEVENT_STATIC_LIBRARIES} stdc++fs pthread teadns::parser spdlog::spdlog) \ No newline at end of file diff --git a/server/main.cpp b/server/main.cpp index b1c48c8..e9217c1 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "src/logger.h" #include "src/server.h" #include "src/handler.h" @@ -14,7 +15,7 @@ std::vector bindings(uint16_t port) { memset(&any_v4, 0, sizeof(sockaddr_in)); any_v4.sin_family = AF_INET; - any_v4.sin_port = htons(port); //htons(53); + any_v4.sin_port = htons(port); any_v4.sin_addr.s_addr = INADDR_ANY; } @@ -23,7 +24,7 @@ std::vector bindings(uint16_t port) { memset(&any_v4, 0, sizeof(sockaddr_in)); any_v4.sin_family = AF_INET; - any_v4.sin_port = htons(port); //htons(53); + any_v4.sin_port = htons(port); any_v4.sin_addr.s_addr = (1UL << 24U) | 127U; } @@ -33,9 +34,15 @@ std::vector bindings(uint16_t port) { extern std::vector le_token; int main(int argc, char** argv) { evthread_use_pthreads(); + std::string error{}; + + if(!logger::setup(error)) { + std::cerr << "Failed to setup log: " << error << "\n"; + return 1; + } if(argc < 2) { - std::cerr << "Missing port. Default DNS port is 53\n"; + log_general()->error("Missing port. Default DNS port is 53"); return 1; } @@ -43,18 +50,19 @@ int main(int argc, char** argv) { try { port = std::stoul(argv[1]); } catch(std::exception& ex) { - std::cerr << "Failed to parse port\n"; + log_general()->error("Failed to parse port ({})", argv[1]); return 1; } - std::string error{}; auto handler = std::make_shared(); DNSServer server{handler}; + log_general()->info("Starting server on port {}", port); if(!server.start(bindings(port), error)) { + auto logger = log_general(); for(auto& binding : server.bindings()) - std::cout << " - " << binding->error << "\n"; - std::cerr << "Failed to start server: " << error << "\n"; + logger->error(" - {}", error); + logger->error("Failed to start server: {}", error); return 1; } @@ -65,21 +73,23 @@ int main(int argc, char** argv) { continue; if(line == "end" || line == "stop") { - std::cout << "Stopping server\n"; + log_general()->info("Stopping server"); break; } else if(line.length() > 13 && line.substr(0, 13) == "add-le-token ") { le_token.push_back(line.substr(13)); - std::cout << "Added letsencrypt token: " << le_token.back() << "\n"; - } else if(line.length() > 14 && line.substr(0, 14) == "clear-le-token ") { - std::cout << "Cleaning up LE tokens\n"; + log_general()->info("Added LetsEncrypt token {}", le_token.back()); + } else if(line.length() >= 14 && line.substr(0, 14) == "clear-le-token") { + log_general()->info("Cleaning up {} le tokens", le_token.size()); le_token.clear(); - } else if(line.length() > 14 && line.substr(0, 14) == "list-le-token ") { - std::cout << "Letsencrypt tokens (" << le_token.size() << "):\n"; + } else if(line.length() >= 14 && line.substr(0, 14) == "list-le-token") { + log_general()->info("LetsEncrypt tokens ({}):", le_token.size()); for(auto& token : le_token) - std::cout << " - " << token << "\n"; + log_general()->info(" - ", token); } else { - std::cerr << "Unknown command \"" << line << "\"\n"; + log_general()->error("Unknown command \"{}\"", line); } } + + logger::shutdown(); return 0; } \ No newline at end of file diff --git a/server/src/handler.cpp b/server/src/handler.cpp index f426ff4..ae0b64e 100644 --- a/server/src/handler.cpp +++ b/server/src/handler.cpp @@ -1,38 +1,48 @@ #include "./handler.h" #include "./server.h" #include "./net.h" +#include "logger.h" #include #include #include #include +#include using namespace ts::dns; using namespace ts::dns::builder; std::vector le_token; + +struct ClientAddress { + std::string str; + const sockaddr_storage &addr; +}; + +void create_answer(DNSBuilder& response, const ClientAddress &client_address, const parser::DNSQuery& query); 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"; + const ClientAddress addr_data{net::to_string(address), address}; + auto logger = log_dns(); + const auto begin = std::chrono::system_clock::now(); + DNSParser parser{0, nullptr, buffer, size}; std::string error; if(!parser.parse(error)) { - std::cout << " Failed to parse request: " << error << "\n"; + logger->warn("[{}] Received invalid DNS request: {}", addr_data.str, error); return; } - - std::cout << " Query type: " << (uint32_t) parser.header().query_type() << "\n"; - std::cout << " Queries (" << parser.queries().size() << "):\n"; + logger->info("[{}] Received DNS request with {} queries", addr_data.str, parser.queries().size()); + logger->trace("[{}] Query type: {}", addr_data.str, (uint32_t) parser.header().query_type()); + logger->trace("[{}] Queries ({}):", addr_data.str, (uint32_t) parser.queries().size()); for(auto& query : parser.queries()) - std::cout << " " << query->qname() << " (" << rrclass::name(query->qclass()) << "::" << rrtype::name(query->qtype()) << ")\n"; - std::cout << " Sending response.\n"; + logger->trace("[{}] {} ({}::{})", addr_data.str, query->qname(), rrclass::name(query->qclass()), rrtype::name(query->qtype())); { 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()) { @@ -41,62 +51,143 @@ void WebDNSHandler::handle_message(const std::shared_ptr& bind 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); - } else if(query->qclass() == rrclass::IN && query->qtype() == rrtype::TXT) { - auto dn = query->qname(); - std::transform(dn.begin(), dn.end(), dn.begin(), tolower); - - if(dn == "_acme-challenge.con-gate.work") { - std::cout << " Letsencrypt request\n"; - std::cout << " Sending predefined key(s)\n"; - - for(auto& key : le_token) { - auto& a = response.push_answer(query->qname()); - a.set_class(query->qclass()); - a.set_type(query->qtype()); - a.set_ttl(120); - a.builder().set_text(key); - } - } + try { + create_answer(response, addr_data, *query); + } catch(std::exception& ex) { + logger->error("[{}] Failed to generate answer for query. Cought an exception: {}", addr_data.str, ex.what()); } } + if(parser.answers().empty()) + response.header().set_response_code(rcode::NXDOMAIN); + else + response.header().set_response_code(rcode::NOERROR); + char rbuffer[1024]; auto len = response.build(rbuffer, 1024, error); if(!len) { - std::cout << " Failed to build response: " << error << "\n"; - return; + //TODO: Send prebuild server fail packet + logger->error("[{}] Failed to build response: {}", error); + } else { + binding->send(address, rbuffer, len); } - binding->send(address, rbuffer, len); - return; } + + const auto end = std::chrono::system_clock::now(); + logger->info("[{}] Query completed in {}us", addr_data.str, std::chrono::floor(end - begin).count()); } + +#define MAX_IPV6_ADDRESS_STR_LEN 39 + +//Address at least one char long! +bool parse_v6(uint8_t(result)[16], const std::string_view& address) { + uint16_t accumulator{0}; + uint8_t colon_count{0}, pos{0}; + + memset(result, 0, 16); + + // Step 1: look for position of ::, and count colons after it + for(uint8_t i = 1; i <= MAX_IPV6_ADDRESS_STR_LEN && address.length() > i; i++) { + if (address[i] == ':') { + if (address[i - 1] == ':') { + colon_count = 14; // Double colon! + } else if (colon_count) { + colon_count -= 2; // Count backwards the number of colons after the :: + } + } + } + + // Step 2: convert from ascii to binary + for(uint8_t i=0; i <= MAX_IPV6_ADDRESS_STR_LEN && pos < 16; i++) { + if (address[i] == ':' || address.length() == i) { + result[pos] = accumulator >> 8U; + result[pos + 1] = accumulator; + accumulator = 0; + + if (colon_count && i && address[i - 1] == ':') { + pos = colon_count; + } else { + pos += 2; + } + } else { + (uint8_t&) address[i] |= 0x20U; + accumulator <<= 4U; + + if (address[i] >= '0' && address[i] <= '9') { + accumulator |= (uint8_t) (address[i] - '0'); + } else if (address[i] >= 'a' && address[i] <= 'f') { + accumulator |= (uint8_t) ((address[i] - 'a') + 10); + } else { + return false; // Not hex or colon: fail + } + } + } + + return true; +} + + +void create_answer(DNSBuilder& response, const ClientAddress & client_address, const parser::DNSQuery& query) { + if(query.qclass() != rrclass::IN) return; + + if(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) + return; + } + + log_dns()->info("[{}] Sending requested IPv4 ({}): {}.{}.{}.{}", client_address.str, dn, resp[0], resp[1], resp[2], resp[3]); + auto& a = response.push_answer(query.qname()); + a.set_class(query.qclass()); + a.set_type(rrtype::A); + a.set_ttl(120); + a.builder().set_address(resp); + } else if(query.qtype() == rrtype::AAAA) { + auto dn = query.qname(); + const auto parts = parse_dn(dn); + + if(parts.size() != 3) return; + + in6_addr result{}; + if(!parse_v6(result.__in6_u.__u6_addr8, parts[0])) return; + + log_dns()->info("[{}] Sending requested IPv6 ({}): {}", client_address.str, dn, net::to_string(result)); + + auto& a = response.push_answer(query.qname()); + a.set_class(query.qclass()); + a.set_type(rrtype::AAAA); + a.set_ttl(120); + a.builder().set_address(result.__in6_u.__u6_addr32); + } else if(query.qtype() == rrtype::TXT) { + auto dn = query.qname(); + std::transform(dn.begin(), dn.end(), dn.begin(), tolower); + + if(dn == "_acme-challenge.con-gate.work") { + log_dns()->info("[{}] Received LetsEncrypt auth request. Sending {} predefined keys.", le_token.size()); + + for(auto& key : le_token) { + auto& a = response.push_answer(query.qname()); + a.set_class(query.qclass()); + a.set_type(query.qtype()); + a.set_ttl(120); + a.builder().set_text(key); + } + } + } +} \ No newline at end of file diff --git a/server/src/logger.cpp b/server/src/logger.cpp new file mode 100644 index 0000000..2198725 --- /dev/null +++ b/server/src/logger.cpp @@ -0,0 +1,50 @@ +#include "./logger.h" + +#include +#include +#include + +namespace fs = std::experimental::filesystem; + +const static std::string log_folder{"logs/"}; + +std::shared_ptr logger::logger_general{nullptr}; +std::shared_ptr logger::logger_network{nullptr}; +std::shared_ptr logger::logger_dns{nullptr}; + +bool logger::setup(std::string& error) { + try { + if(!fs::exists(log_folder) && !fs::create_directories(log_folder)) { + error = "failed to create directory " + log_folder; + return false; + } + } catch(fs::filesystem_error& ex) { + error = "failed to create log folder at " + log_folder + ": " + std::string{ex.what()}; + return false; + } + + std::vector sinks{}; + sinks.reserve(2); + { + auto file_sink = std::make_shared(log_folder + "log_", 0, 0); + file_sink->set_level(spdlog::level::trace); + sinks.push_back(file_sink); + + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::trace); + sinks.push_back(console_sink); + } + + spdlog::register_logger(logger_general = std::make_shared("general", std::begin(sinks), std::end(sinks), 8192, spdlog::async_overflow_policy::block_retry)); + spdlog::register_logger(logger_network = std::make_shared("network", std::begin(sinks), std::end(sinks), 8192, spdlog::async_overflow_policy::block_retry)); + spdlog::register_logger(logger_dns = std::make_shared("dns ", std::begin(sinks), std::end(sinks), 8192, spdlog::async_overflow_policy::block_retry)); + + spdlog::apply_all([](const std::shared_ptr& l) { + l->set_level(spdlog::level::trace); + }); + return true; +} + +bool logger::shutdown() { + spdlog::drop_all(); +} \ No newline at end of file diff --git a/server/src/logger.h b/server/src/logger.h new file mode 100644 index 0000000..e2a976b --- /dev/null +++ b/server/src/logger.h @@ -0,0 +1,17 @@ +#pragma once + +#define SPDLOG_LEVEL_NAMES { "trace ", "debug ", "info ", "warning ", "error ", "critical", "off " } +#include + +namespace logger { + extern std::shared_ptr logger_general; + extern std::shared_ptr logger_network; + extern std::shared_ptr logger_dns; + + extern bool setup(std::string& /* error */); + extern bool shutdown(); +} + +inline std::shared_ptr log_general() { return logger::logger_general; } +inline std::shared_ptr log_network() { return logger::logger_network; } +inline std::shared_ptr log_dns() { return logger::logger_dns; } \ No newline at end of file diff --git a/server/src/server.cpp b/server/src/server.cpp index b930200..1128bd5 100644 --- a/server/src/server.cpp +++ b/server/src/server.cpp @@ -1,6 +1,7 @@ #include "./server.h" #include "./handler.h" #include "./net.h" +#include "logger.h" #include #include @@ -280,18 +281,17 @@ void DNSServer::event_cb_read(evutil_socket_t fd, short, void *ptr_binding) { if(errno == EAGAIN) break; - std::cerr << "Failed to receive data: " << errno << "/" << strerror(errno) << "\n"; + log_network()->warn("Failed to receive data from fd {}: {}/{}", fd, errno, strerror(errno)); 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"; + log_network()->warn("Dropping {} bytes from {} because we've no handler", read_length, net::to_string(source_address, true)); } } @@ -315,7 +315,7 @@ void DNSServer::event_cb_write(evutil_socket_t fd, short, void *ptr_binding) { 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); + log_network()->error("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/util/CMakeLists.txt b/util/CMakeLists.txt index 793cccf..e82b980 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -2,4 +2,6 @@ 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 +target_include_directories(teadns__parser INTERFACE include) + +add_library(teadns::parser ALIAS teadns__parser) \ No newline at end of file diff --git a/util/include/teadns/builder.h b/util/include/teadns/builder.h index 7b2d6a1..c41152b 100644 --- a/util/include/teadns/builder.h +++ b/util/include/teadns/builder.h @@ -143,6 +143,31 @@ namespace ts::dns { uint32_t address{0}; ); + define_builder(AAAA, base, + inline void set_address(const uint32_t (&address)[4]) { + this->set_address(address[0], address[1], address[2], address[3]); + } + + inline void set_address(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { + this->address[0] = a; + this->address[1] = b; + this->address[2] = c; + this->address[3] = d; + } + + inline void set_address(const uint8_t (&address)[16]) { + this->set_address( + (uint32_t) htonl((address[ 0] << 24UL) | (address[ 1] << 16UL) | (address[ 2] << 8UL) | address[ 3]), + (uint32_t) htonl((address[ 4] << 24UL) | (address[ 5] << 16UL) | (address[ 6] << 8UL) | address[ 7]), + (uint32_t) htonl((address[ 8] << 24UL) | (address[ 8] << 16UL) | (address[10] << 8UL) | address[11]), + (uint32_t) htonl((address[12] << 24UL) | (address[13] << 16UL) | (address[14] << 8UL) | address[15]) + ); + } + + private: + uint32_t address[4]{0, 0, 0, 0}; + ); + define_builder(TXT, base, inline void set_text(const std::string& text) { this->_text = text; } private: diff --git a/util/include/teadns/parser.h b/util/include/teadns/parser.h index 5901c20..d48c0c0 100644 --- a/util/include/teadns/parser.h +++ b/util/include/teadns/parser.h @@ -88,6 +88,8 @@ namespace ts::dns { rr_list_t parsed_additionals; }; + std::vector parse_dn(const std::string_view& /* dn */); + namespace parser { #ifndef WIN32 class DNSHeader { diff --git a/util/src/builder.cpp b/util/src/builder.cpp index b5adb98..e75f637 100644 --- a/util/src/builder.cpp +++ b/util/src/builder.cpp @@ -121,6 +121,18 @@ bool rrbuilder::A::build(char *&buffer, size_t &max_size, std::string &error) { return true; } +bool rrbuilder::AAAA::build(char *&buffer, size_t &max_size, std::string &error) { + if(max_size < 16) { + error = "buffer too small"; + return false; + } + + memcpy(buffer, &this->address[0], 16); + buffer += 16; + max_size -= 16; + return true; +} + bool rrbuilder::TXT::build(char *&buffer, size_t &max_size, std::string &error) { if(max_size + 1 < this->_text.size()) { error = "buffer too small"; diff --git a/util/src/parser.cpp b/util/src/parser.cpp index c1362a9..839f7d3 100644 --- a/util/src/parser.cpp +++ b/util/src/parser.cpp @@ -401,3 +401,19 @@ std::string named_base::name() { return this->handle->dns_data()->parse_dns_dn(error, index, true); #endif } + + +std::vector ts::dns::parse_dn(const std::string_view &dn) { + std::vector result{}; + result.reserve(8); + + size_t index{0}, found{0}; + do { + found = dn.find('.', index); + const auto length = (found == -1 ? dn.length() : found) - index; + result.emplace_back(dn.substr(index, length)); + index = found; + } while(++index); + + return result; +} \ No newline at end of file