From 7e87ec772e625b14b3141363daa9b15f3311ace2 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 24 Oct 2019 18:41:23 +0200 Subject: [PATCH 1/3] updated the dns resolver in js part --- modules/renderer/dns/dns_resolver.ts | 257 +++------------------------ 1 file changed, 25 insertions(+), 232 deletions(-) diff --git a/modules/renderer/dns/dns_resolver.ts b/modules/renderer/dns/dns_resolver.ts index ca54d2f..396ccf5 100644 --- a/modules/renderer/dns/dns_resolver.ts +++ b/modules/renderer/dns/dns_resolver.ts @@ -1,245 +1,38 @@ /// - window["require_setup"](module); - -import * as dns_handler from "dns"; +import * as dns_handler from "teaclient_dns"; namespace _dns { - type Lookup = (hostname: string, callback: (err: NodeJS.ErrnoException, result: R) => void) => void; - type AsyncLookup = (hostname: string, timeout: number) => Promise; - function make_async(fun: Lookup) : AsyncLookup { - return (hostname, timeout) => { - return new Promise((resolve, reject) => { - const timeout_id = setTimeout(() => { - reject("timeout"); - }, timeout); - fun(hostname, (err, result) => { - clearTimeout(timeout_id); - if(err) { - if(err.errno as any == "ENOTFOUND" || err.errno as any == "ENODATA") { - resolve(null); - return; - } - - reject(err); - } else - resolve(result); - }) - }); - }; - } - - const async_resolve_srv = make_async(dns_handler.resolveSrv); - const async_resolve_cname = make_async(dns_handler.resolveCname); - const async_resolve_a = make_async(dns_handler.resolve4); - const async_resolve_aaaa = make_async(dns_handler.resolve6); - const async_resolve_any = make_async(dns_handler.resolveAny); - - export interface AddressTarget { - target_ip: string; - target_port?: number; - } - export interface ResolveOptions extends dns.ResolveOptions { - log?: (message: string, ...args: any[]) => void; - override_port?: number; - } - export function supported() { return true; } - export async function resolve_address(address: string, _options?: ResolveOptions) : Promise { - if(address === "localhost") { - return { - target_ip: "localhost" - }; - } - - const options: ResolveOptions = {}; - Object.assign(options, dns.default_options); - Object.assign(options, _options || {}); - - if(options.max_depth <= 0) - throw "max depth exceeded"; - - if(typeof(options.log) !== "function") - options.log = (message, ...args) => console.debug("[DNS] " + message, ...args); - - const mod_options: ResolveOptions = {}; - Object.assign(mod_options, options); - mod_options.max_depth = mod_options.max_depth - 1; - mod_options.allow_srv = false; - mod_options.log = (message, ...args) => options.log(" " + message, ...args); - - options.log("Resolving %s", address); - - let response: AddressTarget; - if(typeof(options.allow_aaaa) !== "boolean" || options.allow_aaaa) { - const aaaa_response: string[] | undefined | null = await async_resolve_aaaa(address, options.timeout).catch(error => { - options.log("AAAA record resolved unsuccessfully (%o)", error); - return Promise.resolve(undefined); - }) as any; - - - if(typeof(aaaa_response) !== "undefined") { - if(!aaaa_response || aaaa_response.length == 0) - options.log("No AAAA records found"); - else { - options.log("Resolved AAAA records: %o. Returning: %s", aaaa_response, aaaa_response[0]); - response = { - target_ip: aaaa_response[0], - target_port: options.override_port - }; - } - } - } - if(!response && (typeof(options.allow_a) !== "boolean" || options.allow_a)) { - const a_response: string[] | undefined | null = await async_resolve_a(address, options.timeout).catch(error => { - options.log("A record resolved unsuccessfully (%o)", error); - return Promise.resolve(undefined); - }) as any; - - if(typeof(a_response) !== "undefined") { - if(!a_response || a_response.length == 0) - options.log("No A records found"); - else { - options.log("Resolved A records: %o. Returning: %s", a_response, a_response[0]); - response = { - target_ip: a_response[0], - target_port: options.override_port - }; - } + export async function resolve_address(address: ServerAddress, _options?: dns.ResolveOptions) : Promise { + /* backwards compatibility */ + if(typeof(address) === "string") { + address = { + host: address, + port: 9987 } } - if(!response && (typeof(options.allow_any) !== "boolean" || options.allow_any)) { - const any_response: dns_handler.AnyRecord[] = await async_resolve_any(address, options.timeout).catch(error => { - options.log("ANY record resolved unsuccessfully (%o)", error); - return Promise.resolve(undefined); + return new Promise((resolve, reject) => { + dns_handler.resolve_cr(address.host, address.port, result => { + if(typeof(result) === "string") + reject(result); + else + resolve({ + target_ip: result.host, + target_port: result.port + }); }); - - - if(typeof(any_response) !== "undefined") { - if(!any_response || any_response.length == 0) - options.log("No ANY records found"); - else { - options.log("Resolved ANY records: %o.", any_response); - for(const record of any_response) { - if(record.type === "A") { - const a_record = record as dns_handler.AnyARecord; - options.log("Returning A record from ANY query: %s", a_record.address); - return { - target_ip: a_record.address, - target_port: options.override_port - }; - } else if(record.type === "AAAA") { - const aaaa_record = record as dns_handler.AnyAaaaRecord; - options.log("Returning AAAA record from ANY query: %s", aaaa_record.address); - return { - target_ip: aaaa_record.address, - target_port: options.override_port - }; - } - } - } - } - } - - if(typeof(options.allow_srv) !== "boolean" || options.allow_srv) { - const response: dns_handler.SrvRecord[] = await async_resolve_srv("_ts3._udp." + address, options.timeout).catch(error => { - options.log("SRV resolve unsuccessfully (%o)", error); - return Promise.resolve(undefined); - }); - - if(typeof(response) !== "undefined") { - if(!response || response.length == 0) - options.log("No SRV records found"); - else { - const sorted = response.sort((a, b) => b.weight - a.weight); - const original_port = mod_options.override_port; - options.log("Resolved SRV records: %o", sorted); - for(const entry of sorted) { - options.log("Resolving SRV record: %o", entry); - mod_options.override_port = entry.port || mod_options.override_port; - const resp = await resolve_address(entry.name, mod_options).catch(error => { - options.log("SRV entry resolved unsuccessfully (%o)", error); - return Promise.resolve(undefined); - }); - if(resp) { - options.log("SRV entry resolved to %o. Found result", resp); - return resp; - } else { - options.log("No response for SRV record"); - } - } - mod_options.override_port = original_port; - } - } - } - - /* resolve CNAME in the last step, else may the A records will be empty! */ - if(typeof(options.allow_cname) !== "boolean" || options.allow_cname) { - const cname_response: string[] = await async_resolve_cname(address, options.timeout).catch(error => { - options.log("CName resolved unsuccessfully (%o)", error); - return Promise.resolve(undefined); - }); - - if(typeof(cname_response) !== "undefined") { - if(!cname_response || cname_response.length == 0) - options.log("No CNAME records found"); - else { - options.log("Resolved %d CNAME records", cname_response.length); - for(const entry of cname_response) { - options.log("Resolving CNAME record: %o", entry); - const resp = await resolve_address(entry, mod_options).catch(error => { - options.log("Failed to resolve resolved CName (%o)", error); - return Promise.resolve(undefined); - }); - if(resp) { - options.log("CName entry resolved to %o. Found result", resp); - return resp; - } else { - options.log("No response for CName record"); - } - } - response = undefined; /* overridden by a CNAME */ - } - } - } - - /* - const lookup_result = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject("timeout"); - }, options.timeout); - dns_handler.lookup(address, { - hints: dns_handler.ADDRCONFIG | dns_handler.V4MAPPED, - all: true, - family: 0 - }, (error, result, family) => { - clearTimeout(timeout); - console.log(result); - if(error) { - if(error.errno as any == "ENOTFOUND" || error.errno as any == "ENODATA") { - resolve(null); - return; - } - - reject(error); - } else - resolve(result); - }); - }).catch(error => { - options.log("General lookup failed: %o", error); - return Promise.resolve(undefined); - }); - console.log(lookup_result); - */ - if(response) - return response; - options.log("No records found, no result."); - return undefined; + }) } - - dns_handler.setServers(["8.8.8.8", "8.8.8.4", "1.1.1.1"]); } -Object.assign(window["dns"] || (window["dns"] = {} as any), _dns); \ No newline at end of file +Object.assign(window["dns"] || (window["dns"] = {} as any), _dns); +loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { + name: "Native DNS initialized", + function: async () => { + dns_handler.initialize(); + }, + priority: 10 +}); \ No newline at end of file From bc94c8d31aa71ce193ef32895b278f544d20e92f Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 24 Oct 2019 18:41:35 +0200 Subject: [PATCH 2/3] A lots of updates --- native/CMakeLists.txt | 6 + native/dist/ext_nan/NanEventCallback.h | 9 + native/dns/CMakeLists.txt | 19 + native/dns/binding.cc | 96 +++ native/dns/src/resolver.cpp | 677 ++++++++++++++++++ native/dns/src/resolver.h | 161 +++++ native/dns/src/response.cpp | 86 +++ native/dns/src/response.h | 145 ++++ native/dns/src/types.cpp | 102 +++ native/dns/src/types.h | 134 ++++ native/dns/test/crash.cpp | 66 ++ native/dns/test/main.cpp | 52 ++ native/dns/test/main.ts | 27 + native/dns/utils.cpp | 497 +++++++++++++ native/dns/utils.h | 33 + native/serverconnection/CMakeLists.txt | 3 +- .../serverconnection/src/audio/AudioInput.cpp | 7 +- .../src/connection/ProtocolHandler.h | 2 +- .../src/connection/ProtocolHandlerPOW.cpp | 36 +- native/serverconnection/test/js/flood.ts | 28 +- 20 files changed, 2147 insertions(+), 39 deletions(-) create mode 100644 native/dns/CMakeLists.txt create mode 100644 native/dns/binding.cc create mode 100644 native/dns/src/resolver.cpp create mode 100644 native/dns/src/resolver.h create mode 100644 native/dns/src/response.cpp create mode 100644 native/dns/src/response.h create mode 100644 native/dns/src/types.cpp create mode 100644 native/dns/src/types.h create mode 100644 native/dns/test/crash.cpp create mode 100644 native/dns/test/main.cpp create mode 100644 native/dns/test/main.ts create mode 100644 native/dns/utils.cpp create mode 100644 native/dns/utils.h diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 1e59da5..f317051 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -133,6 +133,7 @@ else() endfunction() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -static-libgcc -static-libstdc++") + set(CMAKE_SHARED_LIBRARY_CXX_FLAGS "") # Disable -fPIC (We dont want any relonking with build in electron libraries endif() setup_nodejs() @@ -168,3 +169,8 @@ function(build_crash_handler) add_subdirectory(crash_handler) endfunction() build_crash_handler() + +function(build_dns) + add_subdirectory(dns) +endfunction() +build_dns() diff --git a/native/dist/ext_nan/NanEventCallback.h b/native/dist/ext_nan/NanEventCallback.h index 2e7e11e..1c511ee 100644 --- a/native/dist/ext_nan/NanEventCallback.h +++ b/native/dist/ext_nan/NanEventCallback.h @@ -84,6 +84,15 @@ namespace Nan { struct callback_wrap { std::shared_ptr> handle; + void call_cpy(Args... args, bool no_throw = false) { + if(!this->handle) { + if(no_throw) + return; + throw std::bad_function_call(); + } + handle->callback(std::forward(args)...); + } + void call(Args&&... args, bool no_throw = false) { if(!this->handle) { if(no_throw) diff --git a/native/dns/CMakeLists.txt b/native/dns/CMakeLists.txt new file mode 100644 index 0000000..eae7454 --- /dev/null +++ b/native/dns/CMakeLists.txt @@ -0,0 +1,19 @@ +set(MODULE_NAME "teaclient_dns") + +set(SOURCE_FILES ${SOURCE_FILES} src/resolver.cpp src/types.cpp src/response.cpp utils.cpp) + +find_package(Libevent REQUIRED) +include_directories(${LIBEVENT_INCLUDE_DIRS}) +message("EVENT: ${LIBEVENT_INCLUDE_DIRS}") + +#find_package(Unbound REQUIRED) +#include_directories(${Unbound_INCLUDE_DIR}) + +add_nodejs_module(${MODULE_NAME} binding.cc ${SOURCE_FILES}) +target_link_libraries(${MODULE_NAME} /home/wolverindev/TeaSpeak-Client/third_party/unbound/build/lib/libunbound.a ${LIBEVENT_STATIC_LIBRARIES} pthread) + +add_executable(DNS-Test ${SOURCE_FILES} test/main.cpp) +target_link_libraries(DNS-Test /home/wolverindev/TeaSpeak-Client/third_party/unbound/build/lib/libunbound.a ssl crypto ${LIBEVENT_STATIC_LIBRARIES} pthread) + +add_executable(DNS-Test2 test/crash.cpp) +target_link_libraries(DNS-Test2 /home/wolverindev/TeaSpeak-Client/third_party/unbound/build/lib/libunbound.a ssl crypto ${LIBEVENT_STATIC_LIBRARIES} pthread) \ No newline at end of file diff --git a/native/dns/binding.cc b/native/dns/binding.cc new file mode 100644 index 0000000..a578b21 --- /dev/null +++ b/native/dns/binding.cc @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include + +#include "./src/resolver.h" +#include "./utils.h" + +using namespace std; + +#include "NanException.h" +#include "NanEventCallback.h" + +std::unique_ptr resolver{nullptr}; + +NAN_METHOD(initialize) { + if(resolver) { + Nan::ThrowError("already initialized"); + return; + } + + //evthread_use_pthreads(); + resolver = make_unique(); + + string error; + if(!resolver->initialize(error, true, true)) { + Nan::ThrowError(error.c_str()); + return; + } +} + +NAN_METHOD(query_connect_address) { + if(!resolver) { + Nan::ThrowError("initialize resolver first!"); + return; + } + + if(info.Length() != 3 || !info[0]->IsString() || !info[1]->IsNumber() || !info[2]->IsFunction()) { + Nan::ThrowError("invalid arguments"); + return; + } + + auto host = Nan::Utf8String{info[0]->ToString()}; + auto port = info[1]->ToNumber(Nan::GetCurrentContext()).ToLocalChecked()->NumberValue(); + + auto js_callback = make_unique(info[2].As()); + auto begin = chrono::system_clock::now(); + auto callback = Nan::async_callback([js_callback = std::move(js_callback), begin] (bool success, std::string message, tc::dns::ServerAddress response) { + Nan::HandleScope scope{}; + auto isolate = Nan::GetCurrentContext()->GetIsolate(); + + v8::Local argv[1]; + if(!success) { + argv[0] = v8::String::NewFromOneByte(isolate, (uint8_t*) message.c_str()).ToLocalChecked(); + } else { + auto js_data = Nan::New(); + Nan::Set(js_data, + v8::String::NewFromUtf8(isolate, "host").ToLocalChecked(), + v8::String::NewFromUtf8(isolate, response.host.c_str()).ToLocalChecked() + ); + Nan::Set(js_data, + v8::String::NewFromUtf8(isolate, "port").ToLocalChecked(), + Nan::New(response.port) + ); + Nan::Set(js_data, + v8::String::NewFromUtf8(isolate, "timing").ToLocalChecked(), + Nan::New(chrono::floor(chrono::system_clock::now() - begin).count()) + ); + + argv[0] = js_data; + } + js_callback->Call(1, argv); + }).option_destroyed_execute(true); + + tc::dns::cr(*resolver, + tc::dns::ServerAddress{ *host, (uint16_t) port }, + [callback = std::move(callback)] (bool success, std::variant data) mutable { + callback.call_cpy(success, success ? "" : std::get(data), !success ? tc::dns::ServerAddress{"", 0} : std::get(data)); + }); +} + +__attribute__((visibility("default"))) NAN_MODULE_INIT(init) { + Nan::Set(target, + v8::String::NewFromUtf8(Nan::GetCurrentContext()->GetIsolate(), "resolve_cr").ToLocalChecked(), + Nan::GetFunction(Nan::New(query_connect_address)).ToLocalChecked() + ); + + Nan::Set(target, + v8::String::NewFromUtf8(Nan::GetCurrentContext()->GetIsolate(), "initialize").ToLocalChecked(), + Nan::GetFunction(Nan::New(initialize)).ToLocalChecked() + ); +} + +NODE_MODULE(MODULE_NAME, init) \ No newline at end of file diff --git a/native/dns/src/resolver.cpp b/native/dns/src/resolver.cpp new file mode 100644 index 0000000..f72c218 --- /dev/null +++ b/native/dns/src/resolver.cpp @@ -0,0 +1,677 @@ +#include "./resolver.h" +#include "./response.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* for TSDNS */ +#include +#ifdef WIN32 + #include + #define SOCK_NONBLOCK (0) + #define MSG_DONTWAIT (0) +#else + #include +#endif + +using namespace std; +using namespace tc::dns; + +Resolver::Resolver() { + +} + +Resolver::~Resolver() { + this->finalize(); +} + +bool Resolver::initialize(std::string &error, bool hosts, bool resolv) { + if(this->event.loop_active) + this->finalize(); + + this->event.loop_active = true; + this->event.base = event_base_new(); + if(!this->event.base) { + error = "failed to allcoate event base"; + return false; + } + this->event.loop = std::thread(std::bind(&Resolver::event_loop_runner, this)); + + this->ub_ctx = ub_ctx_create_event(this->event.base); + if(!this->ub_ctx) { + this->finalize(); + error = "failed to create ub context"; + return false; + } + + /* Add /etc/hosts */ + auto err = !hosts ? 0 : ub_ctx_hosts((struct ub_ctx*) this->ub_ctx, nullptr); + if(err != 0) { + cerr << "Failed to add hosts file: " << ub_strerror(err) << endl; + } + + /* Add resolv.conf */ + err = !resolv ? 0 : ub_ctx_resolvconf((struct ub_ctx*) this->ub_ctx, nullptr); + if(err != 0) { + cerr << "Failed to add hosts file: " << ub_strerror(err) << endl; + } + + return true; +} + +void Resolver::finalize() { + this->event.loop_active = false; + if(this->event.base) { + auto ret = event_base_loopexit(this->event.base, nullptr); + if(ret != 0) { + cerr << "Failed to exit event base loop: " << ret << endl; + } + } + + { + this->event.condition.notify_one(); + if(this->event.loop.joinable()) + this->event.loop.join(); + } + + { + unique_lock lock(this->request_lock); + auto dns_list = std::move(this->dns_requests); + auto tsdns_list = std::move(this->tsdns_requests); + + for(auto entry : dns_list) { + ub_cancel(this->ub_ctx, entry->ub_id); + + entry->callback(ResultState::ABORT, 0, nullptr); + this->destroy_dns_request(entry); + } + + for(auto entry : tsdns_list) { + entry->callback(ResultState::ABORT, 0, ""); + this->destroy_tsdns_request(entry); + } + lock.unlock(); + } + + ub_ctx_delete((struct ub_ctx*) this->ub_ctx); + this->ub_ctx = nullptr; + + if(this->event.base) { + event_base_free(this->event.base); + this->event.base = nullptr; + } +} + +void Resolver::event_loop_runner() { + while(true) { + { + unique_lock lock{this->event.lock}; + if(!this->event.loop_active) + break; + + this->event.condition.wait(lock); + if(!this->event.loop_active) + break; + } + + event_base_loop(this->event.base, 0); + } +} + +//Call only within the event loop! +void Resolver::destroy_dns_request(Resolver::dns_request *request) { + assert(this_thread::get_id() == this->event.loop.get_id() || !this->event.loop_active); + + { + lock_guard lock{this->request_lock}; + this->dns_requests.erase(std::find(this->dns_requests.begin(), this->dns_requests.end(), request), this->dns_requests.end()); + } + + if(request->register_event) { + event_del_noblock(request->register_event); + event_free(request->register_event); + request->register_event = nullptr; + } + + if(request->timeout_event) { + event_del_noblock(request->timeout_event); + event_free(request->timeout_event); + request->timeout_event = nullptr; + } + delete request; +} + +void Resolver::destroy_tsdns_request(Resolver::tsdns_request *request) { + assert(this_thread::get_id() == this->event.loop.get_id() || !this->event.loop_active); + + { + lock_guard lock{this->request_lock}; + this->tsdns_requests.erase(std::find(this->tsdns_requests.begin(), this->tsdns_requests.end(), request), this->tsdns_requests.end()); + } + + if(request->event_read) { + event_del_noblock(request->event_read); + event_free(request->event_read); + request->event_read = nullptr; + } + + if(request->event_write) { + event_del_noblock(request->event_write); + event_free(request->event_write); + request->event_write = nullptr; + } + + if(request->timeout_event) { + event_del_noblock(request->timeout_event); + event_free(request->timeout_event); + request->timeout_event = nullptr; + } + + if(request->socket > 0) { +#ifndef WIN32 + ::shutdown(request->socket, SHUT_RDWR); +#endif + ::close(request->socket); + request->socket = 0; + } + + delete request; +} + +//--------------- DNS +void Resolver::resolve_dns(const char *name, const rrtype::value &rrtype, const rrclass::value &rrclass, const std::chrono::microseconds& timeout, const dns_callback_t& callback) { + if(!this->event.loop_active) { + callback(ResultState::INITIALISATION_FAILED, 3, nullptr); + return; + } + + auto request = new dns_request{}; + request->resolver = this; + + request->callback = callback; + request->host = name; + request->rrtype = rrtype; + request->rrclass = rrclass; + + request->timeout_event = evtimer_new(this->event.base, [](evutil_socket_t, short, void *_request) { + auto request = static_cast(_request); + request->resolver->evtimer_dns_callback(request); + }, request); + + request->register_event = evuser_new(this->event.base, [](evutil_socket_t, short, void *_request) { + auto request = static_cast(_request); + + auto errc = ub_resolve_event(request->resolver->ub_ctx, request->host.c_str(), (int) request->rrtype, (int) request->rrclass, (void*) request, [](void* _request, int a, void* b, int c, int d, char* e) { + auto request = static_cast(_request); + request->resolver->ub_callback(request, a, b, c, d, e); + }, &request->ub_id); + + if(errc != 0) { + request->callback(ResultState::INITIALISATION_FAILED, errc, nullptr); + request->resolver->destroy_dns_request(request); + } + }, request); + + if(!request->timeout_event || !request->register_event) { + callback(ResultState::INITIALISATION_FAILED, 2, nullptr); + + if(request->timeout_event) + event_free(request->timeout_event); + + if(request->register_event) + event_free(request->register_event); + + delete request; + return; + } + + /* + * Lock here all requests so the event loop cant already delete the request + */ + unique_lock rlock{this->request_lock}; + + { + auto errc = event_add(request->timeout_event, nullptr); + //TODO: Check for error + + evuser_trigger(request->register_event); + } + + { + auto seconds = chrono::floor(timeout); + auto microseconds = chrono::ceil(timeout - seconds); + + timeval tv{seconds.count(), microseconds.count()}; + auto errc = event_add(request->timeout_event, &tv); + + //TODO: Check for error + } + + this->dns_requests.push_back(request); + rlock.unlock(); + + /* Activate the event loop */ + this->event.condition.notify_one(); +} + +void Resolver::evtimer_dns_callback(tc::dns::Resolver::dns_request *request) { + if(request->ub_id > 0) { + auto errc = ub_cancel(this->ub_ctx, request->ub_id); + if(errc != 0) { + cerr << "Failed to cancel DNS request " << request->ub_id << " after timeout (" << errc << "/" << ub_strerror(errc) << ")!" << endl; + } + } + + request->callback(ResultState::DNS_TIMEOUT, 0, nullptr); + this->destroy_dns_request(request); +} + +void Resolver::ub_callback(dns_request* request, int rcode, void *packet, int packet_length, int sec, char *why_bogus) { + if(rcode != 0) { + request->callback(ResultState::DNS_FAIL, rcode, nullptr); + } else { + auto callback = request->callback; + auto data = std::unique_ptr(new DNSResponse{(uint8_t) sec, why_bogus, packet, (size_t) packet_length}); + callback(ResultState::SUCCESS, 0, std::move(data)); + } + + this->destroy_dns_request(request); +} + +thread_local std::vector visited_links; +std::string DNSResponseBuffer::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(); + + 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; +} + +DNSResponseBuffer::~DNSResponseBuffer() { + ::free(this->buffer); +} + +DNSResponse::DNSResponse(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->packet = make_shared(); + this->packet->buffer = (uint8_t*) malloc(size); + this->packet->length = size; + + memcpy(this->packet->buffer, packet, size); +} + +response::DNSHeader DNSResponse::header() const { + return response::DNSHeader{this}; +} + +bool DNSResponse::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->packet->parse_dns_dn(error, index, true); + if(!error.empty()) { + error = "failed to parse query " + to_string(idx) + " dn: " + error; // NOLINT(performance-inefficient-string-concatenation) + goto error_exit; + } + + if(index + 4 > this->packet_length()) { + error = "truncated data for query " + to_string(index); + goto error_exit; + } + + auto type = (rrtype::value) ntohs(*(uint16_t*) (this->packet->buffer + index)); + index += 2; + + auto klass = (rrclass::value) ntohs(*(uint16_t*) (this->packet->buffer + index)); + index += 2; + + this->parsed_queries.emplace_back(new response::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 " + 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 " + 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 " + 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 DNSResponse::parse_rr(std::string &error, size_t &index, bool allow_compressed) { + auto dn = this->packet->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->packet->buffer + index)); + index += 2; + + auto klass = (rrclass::value) ntohs(*(uint16_t*) (this->packet->buffer + index)); + index += 2; + + auto ttl = ntohl(*(uint32_t*) (this->packet->buffer + index)); + index += 4; + + auto payload_length = ntohs(*(uint16_t*) (this->packet->buffer + index)); + index += 2; + + if(index + payload_length > this->packet_length()) { + error = "truncated body"; + return nullptr; + } + + auto response = std::shared_ptr(new response::DNSResourceRecords{this->packet, index, payload_length, ttl, dn, type, klass}); + index += payload_length; + return response; +} + +//---------------------- TSDNS +void Resolver::resolve_tsdns(const char *query, const sockaddr_storage& server_address, const std::chrono::microseconds& timeout, const tc::dns::Resolver::tsdns_callback_t &callback) { + /* create the socket */ + auto socket = ::socket(server_address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0); + if(socket <= 0) { + callback(ResultState::INITIALISATION_FAILED, -1, "failed to allocate socket: " + to_string(errno) + "/" + strerror(errno)); + return; + } + +#ifdef WIN32 + u_long enabled = 0; + auto non_block_rs = ioctlsocket(this->_socket, FIONBIO, &enabled); + if (non_block_rs != NO_ERROR) { + ::close(socket); + callback(ResultState::INITIALISATION_FAILED, -2, "failed to enable nonblock: " + to_string(errno) + "/" + strerror(errno)); + return; + } +#endif + + int opt = 1; + setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); + + auto request = new tsdns_request{}; + + request->resolver = this; + request->callback = callback; + request->socket = socket; + request->timeout_event = evtimer_new(this->event.base, [](evutil_socket_t, short, void *_request) { + auto request = static_cast(_request); + request->resolver->evtimer_tsdns_callback(request); + }, request); + + request->event_read = event_new(this->event.base, socket, EV_READ | EV_PERSIST, [](evutil_socket_t, short, void *_request){ + auto request = static_cast(_request); + request->resolver->event_tsdns_read(request); + }, request); + request->event_write = event_new(this->event.base, socket, EV_WRITE, [](evutil_socket_t, short, void *_request){ + auto request = static_cast(_request); + request->resolver->event_tsdns_write(request); + }, request); + + if(!request->timeout_event || !request->event_write || !request->event_read) { + callback(ResultState::INITIALISATION_FAILED, -3, ""); + this->destroy_tsdns_request(request); + return; + } + + request->write_buffer = query; + request->write_buffer += "\n\r\r\r\n"; + + int result = ::connect(socket, reinterpret_cast (&server_address), sizeof(server_address)); + if (result < 0) { +#ifdef WIN32 + auto error = WSAGetLastError(); + + if(error != WSAEWOULDBLOCK) { + /* + * TODO! + wchar_t *s = nullptr; + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&s, + 0, + nullptr + ); + fprintf(stdout, "Connect failed with code %d. Error: %ld/%S\n", result, error, s); + LocalFree(s); + */ + + callback(ResultState::TSDNS_CONNECTION_FAIL, -1, "Failed to connect"); + this->destroy_tsdns_request(request); + } +#else + if(errno != EINPROGRESS) { + callback(ResultState::TSDNS_CONNECTION_FAIL, -1, "Failed to connect with code: " + to_string(errno) + "/" + strerror(errno)); + this->destroy_tsdns_request(request); + return; + } +#endif + } + + event_add(request->event_write, nullptr); + event_add(request->event_read, nullptr); + + { + auto seconds = chrono::floor(timeout); + auto microseconds = chrono::ceil(timeout - seconds); + + timeval tv{seconds.count(), microseconds.count()}; + auto errc = event_add(request->timeout_event, &tv); + + //TODO: Check for error + } + + { + lock_guard lock{this->request_lock}; + this->tsdns_requests.push_back(request); + } + + /* Activate the event loop */ + this->event.condition.notify_one(); +} + +void Resolver::evtimer_tsdns_callback(Resolver::tsdns_request *request) { + request->callback(ResultState::DNS_TIMEOUT, 0, ""); + this->destroy_tsdns_request(request); +} + +void Resolver::event_tsdns_read(Resolver::tsdns_request *request) { + int64_t buffer_length = 1024; + char buffer[1024]; + + buffer_length = recv(request->socket, buffer, (int) buffer_length, MSG_DONTWAIT); + if(buffer_length < 0) { +#ifdef WIN32 + auto error = WSAGetLastError(); + if(error != WSAEWOULDBLOCK) + return; + request->callback(ResultState::TSDNS_CONNECTION_FAIL, -2, "read failed: " + to_string(error)); +#else + if(errno == EAGAIN) + return; + request->callback(ResultState::TSDNS_CONNECTION_FAIL, -2, "read failed: " + to_string(errno) + "/" + strerror(errno)); +#endif + this->destroy_tsdns_request(request); + return; + } else if(buffer_length == 0) { + if(request->read_buffer.empty()) { + request->callback(ResultState::TSDNS_EMPTY_RESPONSE, 0, ""); + } else { + request->callback(ResultState::SUCCESS, 0, request->read_buffer); + } + this->destroy_tsdns_request(request); + return; + } + + lock_guard lock{request->buffer_lock}; + request->read_buffer.append(buffer, buffer_length); +} + +void Resolver::event_tsdns_write(Resolver::tsdns_request *request) { + lock_guard lock{request->buffer_lock}; + if(request->write_buffer.empty()) + return; + + auto written = send(request->socket, request->write_buffer.data(), min(request->write_buffer.size(), 1024UL), MSG_DONTWAIT); + if(written < 0) { +#ifdef WIN32 + auto error = WSAGetLastError(); + if(error != WSAEWOULDBLOCK) + return; + request->callback(ResultState::TSDNS_CONNECTION_FAIL, -4, "write failed: " + to_string(error)); +#else + if(errno == EAGAIN) + return; + request->callback(ResultState::TSDNS_CONNECTION_FAIL, -4, "write failed: " + to_string(errno) + "/" + strerror(errno)); +#endif + this->destroy_tsdns_request(request); + return; + } else if(written == 0) { + request->callback(ResultState::TSDNS_CONNECTION_FAIL, -5, "remote peer hang up"); + this->destroy_tsdns_request(request); + return; + } + + if(written == request->write_buffer.size()) + request->write_buffer.clear(); + else { + request->write_buffer = request->write_buffer.substr(written); + event_add(request->event_write, nullptr); + } +} \ No newline at end of file diff --git a/native/dns/src/resolver.h b/native/dns/src/resolver.h new file mode 100644 index 0000000..775a720 --- /dev/null +++ b/native/dns/src/resolver.h @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "./types.h" + +struct ub_ctx; +namespace tc::dns { + namespace response { + class DNSHeader; + class DNSQuery; + class DNSResourceRecords; + } + + struct DNSResponseBuffer { + uint8_t* buffer{nullptr}; + size_t length{0}; + + ~DNSResponseBuffer(); + std::string parse_dns_dn(std::string& /* error */, size_t& /* index */, bool /* compression allowed */); + }; + + class Resolver; + class DNSResponse { + friend class Resolver; + public: + typedef std::vector> rr_list_t; + typedef std::vector> q_list_t; + + DNSResponse(const DNSResponse&) = delete; + DNSResponse(DNSResponse&&) = delete; + + bool parse(std::string& /* error */); + + [[nodiscard]] inline const std::string why_bogus() const { return this->bogus; } + + [[nodiscard]] inline const uint8_t* packet_data() const { return this->packet->buffer; } + [[nodiscard]] inline size_t packet_length() const { return this->packet->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]] response::DNSHeader header() const; + + [[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: + DNSResponse(uint8_t /* secure state */, const char* /* bogus */, void* /* packet */, size_t /* length */); + + std::shared_ptr parse_rr(std::string& /* error */, size_t& index, bool /* compression allowed dn */); + + std::string bogus; + uint8_t secure_state{0}; + + std::shared_ptr packet{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; + }; + + class Resolver { + public: + struct ResultState { + enum value : uint8_t { + SUCCESS = 0, + + INITIALISATION_FAILED = 0x01, + + DNS_TIMEOUT = 0x10, + DNS_FAIL = 0x11, /* error detail is a DNS error code */ + + TSDNS_CONNECTION_FAIL = 0x20, + TSDNS_EMPTY_RESPONSE = 0x21, + + ABORT = 0xFF /* request has been aborted */ + }; + }; + + typedef std::function /* response */)> dns_callback_t; + typedef std::function tsdns_callback_t; + + Resolver(); + virtual ~Resolver(); + + bool initialize(std::string& /* error */, bool /* use hosts */, bool /* use resolv */); + void finalize(); + + void resolve_dns(const char* /* name */, const rrtype::value& /* rrtype */, const rrclass::value& /* rrclass */, const std::chrono::microseconds& /* timeout */, const dns_callback_t& /* callback */); + void resolve_tsdns(const char* /* name */, const sockaddr_storage& /* server */, const std::chrono::microseconds& /* timeout */, const tsdns_callback_t& /* callback */); + private: + struct dns_request { + Resolver* resolver{nullptr}; + + int ub_id{0}; + struct ::event* timeout_event{nullptr}; + struct ::event* register_event{nullptr}; + + std::string host; + dns::rrtype::value rrtype{dns::rrtype::Unassigned}; + dns::rrclass::value rrclass{dns::rrclass::IN}; + + dns_callback_t callback{}; + }; + + struct tsdns_request { + Resolver* resolver{nullptr}; + + int socket{0}; + + struct ::event* timeout_event{nullptr}; + struct ::event* event_read{nullptr}; + struct ::event* event_write{nullptr}; + + std::mutex buffer_lock{}; + std::string write_buffer{}; + std::string read_buffer{}; + + tsdns_callback_t callback{}; + }; + + struct { + bool loop_active{false}; + std::condition_variable condition{}; + std::mutex lock{}; + std::thread loop{}; + event_base* base = nullptr; + } event; + + struct ub_ctx* ub_ctx = nullptr; + + std::vector tsdns_requests{}; + std::vector dns_requests{}; + std::recursive_mutex request_lock{}; /* this is recursive because due to the instance callback resolve_dns could be called recursively */ + + void destroy_dns_request(dns_request*); + void destroy_tsdns_request(tsdns_request*); + + void event_loop_runner(); + + void evtimer_tsdns_callback(tsdns_request* /* request */); + void evtimer_dns_callback(dns_request* /* request */); + + void event_tsdns_write(tsdns_request* /* request */); + void event_tsdns_read(tsdns_request* /* request */); + + void ub_callback(dns_request* /* request */, int /* rcode */, void* /* packet */, int /* packet_len */, int /* sec */, char* /* why_bogus */); + }; +} \ No newline at end of file diff --git a/native/dns/src/response.cpp b/native/dns/src/response.cpp new file mode 100644 index 0000000..32df875 --- /dev/null +++ b/native/dns/src/response.cpp @@ -0,0 +1,86 @@ +#include "./response.h" +#include "./resolver.h" + +#ifdef WIN32 + #include + #include +#else + #include + #include +#endif + +using namespace tc::dns::response; +using namespace tc::dns::response::rrparser; + +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); +} + +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->packet = std::move(packet); +} + +const uint8_t* DNSResourceRecords::payload_data() const { + return this->packet->buffer + this->offset; +} + +//---------------- AAAA +std::string AAAA::address_string() { + return to_string(this->address()); +} + +in6_addr AAAA::address() { + return { + .__in6_u = { + .__u6_addr32 = { + ((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] + } + } + }; +} + +//---------------- SRV +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_packet()->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_packet()->parse_dns_dn(error, index, true); +} + +//---------------- All types with a name +bool named_base::is_valid() { + size_t index = this->handle->payload_offset(); + std::string error{}; + this->handle->dns_packet()->parse_dns_dn(error, index, true); + return error.empty(); +} + +std::string named_base::name() { + size_t index = this->handle->payload_offset(); + std::string error{}; + return this->handle->dns_packet()->parse_dns_dn(error, index, true); +} diff --git a/native/dns/src/response.h b/native/dns/src/response.h new file mode 100644 index 0000000..7bdcd92 --- /dev/null +++ b/native/dns/src/response.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include + +#include "./types.h" + +namespace tc::dns { + class DNSResponse; + class DNSResponseBuffer; + + namespace response { + class DNSHeader { + friend class tc::dns::DNSResponse; + 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 DNSResponse* response) : response{response} {} + const DNSResponse* response{nullptr}; + }; + + class DNSQuery { + friend class tc::dns::DNSResponse; + 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 tc::dns::DNSResponse; + public: + [[nodiscard]] inline std::string qname() const { return this->name; } + [[nodiscard]] inline rrtype::value atype() const { return this->type; } + [[nodiscard]] inline rrclass::value aclass() const { return this->klass; } + [[nodiscard]] inline uint16_t attl() const { return this->ttl; } + + [[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; } + + [[nodiscard]] inline std::shared_ptr dns_packet() const { return this->packet; } + + template + [[nodiscard]] inline T parse() const { + if(T::type != this->type) + throw std::logic_error{"parser type mismatch"}; + return T{this}; + } + private: + DNSResourceRecords(std::shared_ptr, size_t, size_t, uint32_t, std::string , rrtype::value, rrclass::value); + + std::shared_ptr packet{nullptr}; + size_t offset{0}; + size_t length{0}; + + uint32_t ttl; + + std::string name; + rrtype::value type; + rrclass::value klass; + }; + + 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 response::DNSResourceRecords; \ + public: \ + static constexpr auto type = rrtype::name; \ + __VA_ARGS__ \ + private: \ + explicit name(const DNSResourceRecords* handle) : base{handle} {} \ + } + + define_parser(A, base, + [[nodiscard]] inline bool is_valid() { return this->handle->payload_length() == 4; } + [[nodiscard]] inline std::string address_string() { + 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); + } + [[nodiscard]] inline in_addr address() { + return {*(uint32_t*) this->handle->payload_data()}; + } + ); + + define_parser(AAAA, base, + [[nodiscard]] inline bool is_valid() { return this->handle->payload_length() == 16; } + [[nodiscard]] std::string address_string(); + [[nodiscard]] in6_addr address(); + ); + + define_parser(SRV, base, + [[nodiscard]] bool is_valid(); + + [[nodiscard]] inline uint16_t priority() { return ntohs(((uint16_t*) this->handle->payload_data())[0]); } + [[nodiscard]] inline uint16_t weight() { return ntohs(((uint16_t*) this->handle->payload_data())[1]); } + [[nodiscard]] inline uint16_t target_port() { return ntohs(((uint16_t*) this->handle->payload_data())[2]); } + [[nodiscard]] std::string target_hostname(); + ); + + define_parser(CNAME, named_base); + }; + } +} \ No newline at end of file diff --git a/native/dns/src/types.cpp b/native/dns/src/types.cpp new file mode 100644 index 0000000..00c4636 --- /dev/null +++ b/native/dns/src/types.cpp @@ -0,0 +1,102 @@ +#include "types.h" + +std::map tc::dns::rrtype::names{ + {tc::dns::rrtype::A, "A"}, + {tc::dns::rrtype::NS, "NS"}, + {tc::dns::rrtype::MD, "MD"}, + {tc::dns::rrtype::MF, "MF"}, + {tc::dns::rrtype::CNAME, "CNAME"}, + {tc::dns::rrtype::SOA, "SOA"}, + {tc::dns::rrtype::MB, "MB"}, + {tc::dns::rrtype::MG, "MG"}, + {tc::dns::rrtype::MR, "MR"}, + {tc::dns::rrtype::NULL_, "NULL_"}, + {tc::dns::rrtype::WKS, "WKS"}, + {tc::dns::rrtype::PTR, "PTR"}, + {tc::dns::rrtype::HINFO, "HINFO"}, + {tc::dns::rrtype::MINFO, "MINFO"}, + {tc::dns::rrtype::MX, "MX"}, + {tc::dns::rrtype::TXT, "TXT"}, + {tc::dns::rrtype::RP, "RP"}, + {tc::dns::rrtype::AFSDB, "AFSDB"}, + {tc::dns::rrtype::X25, "X25"}, + {tc::dns::rrtype::ISDN, "ISDN"}, + {tc::dns::rrtype::RT, "RT"}, + {tc::dns::rrtype::NSAP, "NSAP"}, + {tc::dns::rrtype::NSAP_PTR, "NSAP_PTR"}, + {tc::dns::rrtype::SIG, "SIG"}, + {tc::dns::rrtype::KEY, "KEY"}, + {tc::dns::rrtype::PX, "PX"}, + {tc::dns::rrtype::GPOS, "GPOS"}, + {tc::dns::rrtype::AAAA, "AAAA"}, + {tc::dns::rrtype::LOC, "LOC"}, + {tc::dns::rrtype::NXT, "NXT"}, + {tc::dns::rrtype::EID, "EID"}, + {tc::dns::rrtype::NIMLOC, "NIMLOC"}, + {tc::dns::rrtype::SRV, "SRV"}, + {tc::dns::rrtype::ATMA, "ATMA"}, + {tc::dns::rrtype::NAPTR, "NAPTR"}, + {tc::dns::rrtype::KX, "KX"}, + {tc::dns::rrtype::CERT, "CERT"}, + {tc::dns::rrtype::A6, "A6"}, + {tc::dns::rrtype::DNAME, "DNAME"}, + {tc::dns::rrtype::SINK, "SINK"}, + {tc::dns::rrtype::OPT, "OPT"}, + {tc::dns::rrtype::APL, "APL"}, + {tc::dns::rrtype::DS, "DS"}, + {tc::dns::rrtype::SSHFP, "SSHFP"}, + {tc::dns::rrtype::IPSECKEY, "IPSECKEY"}, + {tc::dns::rrtype::RRSIG, "RRSIG"}, + {tc::dns::rrtype::NSEC, "NSEC"}, + {tc::dns::rrtype::DNSKEY, "DNSKEY"}, + {tc::dns::rrtype::DHCID, "DHCID"}, + {tc::dns::rrtype::NSEC3, "NSEC3"}, + {tc::dns::rrtype::NSEC3PARAM, "NSEC3PARAM"}, + {tc::dns::rrtype::TLSA, "TLSA"}, + {tc::dns::rrtype::SMIMEA, "SMIMEA"}, + {tc::dns::rrtype::Unassigned, "Unassigned"}, + {tc::dns::rrtype::HIP, "HIP"}, + {tc::dns::rrtype::NINFO, "NINFO"}, + {tc::dns::rrtype::RKEY, "RKEY"}, + {tc::dns::rrtype::TALINK, "TALINK"}, + {tc::dns::rrtype::CDS, "CDS"}, + {tc::dns::rrtype::CDNSKEY, "CDNSKEY"}, + {tc::dns::rrtype::OPENPGPKEY, "OPENPGPKEY"}, + {tc::dns::rrtype::CSYNC, "CSYNC"}, + {tc::dns::rrtype::ZONEMD, "ZONEMD"}, + {tc::dns::rrtype::SPF, "SPF"}, + {tc::dns::rrtype::UINFO, "UINFO"}, + {tc::dns::rrtype::UID, "UID"}, + {tc::dns::rrtype::GID, "GID"}, + {tc::dns::rrtype::UNSPEC, "UNSPEC"}, + {tc::dns::rrtype::NID, "NID"}, + {tc::dns::rrtype::L32, "L32"}, + {tc::dns::rrtype::L64, "L64"}, + {tc::dns::rrtype::LP, "LP"}, + {tc::dns::rrtype::EUI48, "EUI48"}, + {tc::dns::rrtype::EUI64, "EUI64"}, + {tc::dns::rrtype::TKEY, "TKEY"}, + {tc::dns::rrtype::TSIG, "TSIG"}, + {tc::dns::rrtype::IXFR, "IXFR"}, + {tc::dns::rrtype::AXFR, "AXFR"}, + {tc::dns::rrtype::MAILB, "MAILB"}, + {tc::dns::rrtype::MAILA, "MAILA"}, + {tc::dns::rrtype::ANY, "ANY"}, + {tc::dns::rrtype::URI, "URI"}, + {tc::dns::rrtype::CAA, "CAA"}, + {tc::dns::rrtype::AVC, "AVC"}, + {tc::dns::rrtype::DOA, "DOA"}, + {tc::dns::rrtype::AMTRELAY, "AMTRELAY"}, + {tc::dns::rrtype::TA, "TA"}, + {tc::dns::rrtype::DLV, "DLV"}, + {tc::dns::rrtype::Reserved, "Reserved"}, +}; + + +std::map tc::dns::rrclass::names{ + {tc::dns::rrclass::IN, "IN"}, + {tc::dns::rrclass::CH, "CH"}, + {tc::dns::rrclass::HS, "HS"}, + {tc::dns::rrclass::QCLASS_ANY, "QCLASS_ANY"}, + {tc::dns::rrclass::QCLASS_NONE, "QCLASS_NONE"}, +}; \ No newline at end of file diff --git a/native/dns/src/types.h b/native/dns/src/types.h new file mode 100644 index 0000000..8a81e3d --- /dev/null +++ b/native/dns/src/types.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +namespace tc::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 { + 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"; + } + }; +} \ No newline at end of file diff --git a/native/dns/test/crash.cpp b/native/dns/test/crash.cpp new file mode 100644 index 0000000..a1715eb --- /dev/null +++ b/native/dns/test/crash.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ITERATIONS (2000) + +int main() { + /* Setting up all libevent stuff */ + std::mutex ev_lock{}; + std::condition_variable ev_condition{}; + auto ev_base = event_base_new(); + + std::thread ev_loop = std::thread([&]{ + /* Wait until we should start the event loop */ + { + std::unique_lock lock{ev_lock}; + ev_condition.wait(lock); + } + + /* executing the event loop until no events are registered */ + event_base_loop(ev_base, 0); + }); + + + /* Setting up libunbound */ + auto ctx = ub_ctx_create_event(ev_base); + assert(ctx); + + auto errc = ub_ctx_hosts(ctx, nullptr); + assert(errc == 0); + + errc = ub_ctx_resolvconf(ctx, nullptr); + assert(errc == 0); + + int callback_call_count{0}; + for(int i = 0; i < ITERATIONS; i++){ + auto host = std::to_string(i) + "ts.teaspeak.de"; + errc = ub_resolve_event(ctx, host.c_str(), 33, 1, (void*) &callback_call_count, [](void* _ccc, int, void*, int, int, char*) { + *static_cast(_ccc) += 1; + }, nullptr); + assert(errc == 0); + + if(i == 0) { + //CASE 1: CRASH + //Start the event loop as soon the first resolve has been scheduled + ev_condition.notify_one(); + } + } + + //CASE 2: NO CRASH + //Start the event loop after all resolves have been scheduled + //ev_condition.notify_one(); + + /* Hang up until no more events */ + ev_loop.join(); + assert(ITERATIONS == callback_call_count); + + ub_ctx_delete(ctx); + event_base_free(ev_base); +} \ No newline at end of file diff --git a/native/dns/test/main.cpp b/native/dns/test/main.cpp new file mode 100644 index 0000000..f77d6ad --- /dev/null +++ b/native/dns/test/main.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/response.h" +#include "../src/resolver.h" +#include "../utils.h" + +using namespace tc::dns; +using namespace std; + +namespace parser = response::rrparser; + +int main() { + evthread_use_pthreads(); + //evthread_enable_lock_debugging(); + + std::string error{}; + Resolver resolver{}; + + if(!resolver.initialize(error, true, true)) { + cerr << "failed to initialize resolver: " << error << endl; + return 1; + } + + auto begin = chrono::system_clock::now(); + for(int i = 0; i < 250; i++) { + cr(resolver, { + to_string(i) + "ts.twerion.net", + 9922 + }, [begin](bool success, auto data) { + auto end = chrono::system_clock::now(); + cout << "Success: " << success << " Time: " << chrono::ceil(end - begin).count() << "ms" << endl; + if(success) { + auto address = std::get(data); + cout << "Target: " << address.host << ":" << address.port << endl; + } else { + cout << "Message: " << std::get(data) << endl; + } + }); + } + + this_thread::sleep_for(chrono::seconds{8}); + resolver.finalize(); + + return 0; +} \ No newline at end of file diff --git a/native/dns/test/main.ts b/native/dns/test/main.ts new file mode 100644 index 0000000..d02a11d --- /dev/null +++ b/native/dns/test/main.ts @@ -0,0 +1,27 @@ +/// + +console.log("HELLO WORLD"); +module.paths.push("../../build/linux_x64"); +module.paths.push("../../build/win32_64"); + +const original_require = require; +require = (module => original_require("/home/wolverindev/TeaSpeak-Client/client/native/build/linux_x64/" + module + ".node")) as any; +import * as handle from "teaclient_dns"; +require = original_require; + +handle.initialize(); +console.log("INITS"); +handle.resolve_cr("voice.teamspeak.com", 9987, result => { + console.log("Result: " + JSON.stringify(result)); +}); +handle.resolve_cr("ts.twerion.net", 9987, result => { + console.log("Result: " + JSON.stringify(result)); +}); +handle.resolve_cr("twerion.net", 9987, result => { + console.log("Result: " + JSON.stringify(result)); +}); +/* +handle.resolve_cr("localhost", 9987, result => { + console.log("Result: " + JSON.stringify(result)); +}); +*/ \ No newline at end of file diff --git a/native/dns/utils.cpp b/native/dns/utils.cpp new file mode 100644 index 0000000..730437f --- /dev/null +++ b/native/dns/utils.cpp @@ -0,0 +1,497 @@ +#include "./utils.h" +#include "./src/response.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace tc::dns; +namespace parser = tc::dns::response::rrparser; + +//-------------------------- IP-Resolve +struct CrIpStatus { + std::mutex pending_lock; + uint8_t pending{0}; + + ServerAddress original{"", 0}; + + std::tuple a{false, "unset"}; + std::tuple aaaa{false, "unset"}; + + cr_callback_t callback; + std::function&)> finish_callback; + + void one_finished(const std::shared_ptr& _this) { + assert(&*_this == this); + + std::lock_guard lock{this->pending_lock}; + if(--pending == 0) + this->finish_callback(_this); + } +}; + +void cr_ip_finish(const std::shared_ptr& status) { + if(std::get<0>(status->a)) { + status->callback(true, ServerAddress{std::get<1>(status->a), status->original.port}); + } else if(std::get<0>(status->aaaa)) { + status->callback(true, ServerAddress{std::get<1>(status->aaaa), status->original.port}); + } else { + status->callback(false, "failed to resolve an IP for " + status->original.host + ": A{" + std::get<1>(status->a) + "} AAAA{" + std::get<1>(status->aaaa) + "}"); + } +} + +void tc::dns::cr_ip(Resolver& resolver, const ServerAddress& address, const cr_callback_t& callback) { + auto status = std::make_shared(); + + status->original = address; + status->finish_callback = cr_ip_finish; + status->callback = callback; + + /* general pending so we could finish our method */ + status->pending++; + + status->pending++; + resolver.resolve_dns(address.host.c_str(), rrtype::A, rrclass::IN, std::chrono::seconds{5}, [status, &resolver](Resolver::ResultState::value state, int code, std::unique_ptr data){ + if(state != 0) { + status->a = {false, "A query failed. State: " + std::to_string(state) + ", Code: " + std::to_string(code)}; + status->one_finished(status); + return; + } + std::string error; + if(!data->parse(error)) { + status->a = {false, "A query failed. State: " + std::to_string(state) + ", Code: " + std::to_string(code)}; + status->one_finished(status); + return; + } + + for(const auto& answer : data->answers()) { + if(answer->atype() != rrtype::A){ + std::cerr << "Received a non A record answer in A query!" << std::endl; + continue; + } + + auto data = answer->parse(); + if(!data.is_valid()) + continue; + + status->a = {true, data.address_string()}; + status->one_finished(status); + return; + } + + status->a = {false, "empty response"}; + status->one_finished(status); + }); + + status->pending++; + resolver.resolve_dns(address.host.c_str(), rrtype::AAAA, rrclass::IN, std::chrono::seconds{5}, [status, &resolver](Resolver::ResultState::value state, int code, std::unique_ptr data){ + if(state != 0) { + status->aaaa = {false, "AAAA query failed. State: " + std::to_string(state) + ", Code: " + std::to_string(code)}; + status->one_finished(status); + return; + } + + std::string error; + if(!data->parse(error)) { + status->aaaa = {false, "failed to parse AAAA query reponse: " + error}; + status->one_finished(status); + return; + } + + for(const auto& answer : data->answers()) { + if(answer->atype() != rrtype::AAAA){ + std::cerr << "Received a non AAAA record answer in AAAA query!" << std::endl; + continue; + } + + auto data = answer->parse(); + if(!data.is_valid()) + continue; + + status->aaaa = {true, data.address_string()}; + status->one_finished(status); + return; + } + + status->aaaa = {false, "empty response"}; + status->one_finished(status); + return; + }); + + status->one_finished(status); +} + +//-------------------------- SRV-Resolve +static std::random_device srv_rnd_dev; +static std::mt19937 srv_rnd(srv_rnd_dev()); + +/* connect resolve for TS3 srv records */ +void tc::dns::cr_srv(Resolver& resolver, const ServerAddress& address, const cr_callback_t& callback, const std::string& application) { + auto query = application + "." + address.host; + resolver.resolve_dns(query.c_str(), rrtype::SRV, rrclass::IN, std::chrono::seconds{5}, [callback, address, &resolver](Resolver::ResultState::value state, int code, std::unique_ptr data){ + if(state != 0) { + callback(false, "SRV query failed. State: " + std::to_string(state) + ", Code: " + std::to_string(code)); + return; + } + + std::string error; + if(!data->parse(error)) { + callback(false, "failed to parse srv query reponse: " + error); + return; + } + + struct SrvEntry { + uint16_t weight; + std::string target; + uint16_t port; + }; + std::map> entries{}; + + auto answers = data->answers(); + for(const auto& answer : answers) { + if(answer->atype() != rrtype::SRV) { + std::cerr << "Received a non SRV record answer in SRV query (" << rrtype::name(answer->atype()) << ")!" << std::endl; + continue; + } + + auto srv = answer->parse(); + entries[srv.priority()].push_back({srv.weight(), srv.target_hostname(), srv.target_port()}); + } + + if(entries.empty()) { + callback(false, "empty response"); + return; + } + + std::deque> results{}; + for(auto [priority, pentries] : entries) { + int64_t count = 0; + for(const auto& entry : pentries) + count += std::max((size_t) entry.weight, 1UL); + + std::uniform_int_distribution dist(0, count - 1); + auto index = dist(srv_rnd); + + count = 0; + for(const auto& entry : pentries) { + count += std::max((size_t) entry.weight, 1UL); + if(count > index) { + count = -1; + results.emplace_back(priority, entry); + break; + } + } + } + assert(!results.empty()); + + std::sort(results.begin(), results.end(), [](const auto& a, const auto& b) { return std::get<0>(a) < std::get<0>(b); }); + + //TODO: Resolve backup stuff as well + auto target = std::get<1>(*results.begin()); + cr_ip(resolver, { + target.target, + target.port == 0 ? address.port : target.port + }, [callback](bool success, auto response) { + if(!success) { + //TODO: Use the backup stuff? + callback(false, "failed to resolve dns pointer: " + std::get(response)); + return; + } + + callback(true, std::get(response)); + }); + }); +} + +//-------------------------- TSDNS-Resolve +void tc::dns::cr_tsdns(tc::dns::Resolver &resolver, const tc::dns::ServerAddress &address, const tc::dns::cr_callback_t &callback) { + auto root = domain_root(address.host); + cr_srv(resolver, {root, 0}, [&resolver, callback, address](bool success, auto data){ + if(!success) { + callback(false, "failed to resolve tsdns address: " + std::get(data)); + return; + } + auto tsdns_host = std::get(data); + + sockaddr_storage tsdns_address{}; + memset(&tsdns_address, 0, sizeof(tsdns_address)); + + auto tsdns_a6 = (sockaddr_in6*) &tsdns_address; + auto tsdns_a4 = (sockaddr_in*) &tsdns_address; + if(inet_pton(AF_INET6, tsdns_host.host.c_str(), &tsdns_a6->sin6_addr) == 1) { + tsdns_a6->sin6_family = AF_INET6; + tsdns_a6->sin6_port = htons(tsdns_host.port == 0 ? 41144 : tsdns_host.port); + } else if(inet_pton(AF_INET, tsdns_host.host.c_str(), &(tsdns_a4->sin_addr)) == 1) { + tsdns_a4->sin_family = AF_INET; + tsdns_a4->sin_port = htons(tsdns_host.port == 0 ? 41144 : tsdns_host.port); + } else { + callback(false, "invalid tsdns host: " + tsdns_host.host); + return; + } + + auto query = address.host; + std::transform(query.begin(), query.end(), query.begin(), tolower); + resolver.resolve_tsdns(query.c_str(), tsdns_address, std::chrono::seconds{5}, [callback, query, address](Resolver::ResultState::value error, int detail, const std::string& response) { + if(error == Resolver::ResultState::SUCCESS) { + if(response == "404") + callback(false, "no record found for " + query); + else { + std::string host{response}; + std::string port{"$PORT"}; + + //TODO: Backup IP-Addresses? + if(host.find(',') != -1) + host = std::string{host.begin(), host.begin() + host.find(',')}; + + auto colon_index = host.rfind(':'); + if(colon_index > 0 && (host[colon_index - 1] == ']' || host.find(':') == colon_index)) { + port = host.substr(colon_index + 1); + host = host.substr(0, colon_index); + } + + ServerAddress resp{host, 0}; + if(port == "$PORT") { + resp.port = address.port; + } else { + try { + resp.port = stoul(port); + } catch(std::exception& ex) { + callback(false, "failed to parse response: " + response + " Failed to parse port: " + port); + return; + } + } + callback(true, resp); + } + } else { + callback(false, "query failed. Code: " + std::to_string(error) + "," + std::to_string(detail) + ": " + response); + } + }); + }, "_tsdns._tcp"); +} + +//-------------------------- Full-Resolve + +struct CrStatus { + enum State { + PENDING, + FAILED, + SUCCESS + }; + + std::recursive_mutex pending_lock; /* do_done could be called recursively because DNS request could answer instant! */ + uint8_t pending{0}; + bool finished{false}; + + tc::dns::ServerAddress address; + tc::dns::cr_callback_t callback; + + ~CrStatus() { + assert(this->pending == 0); + } + + void do_done(const std::shared_ptr& _this) { + std::lock_guard lock{pending_lock}; + this->finished |= this->try_answer(_this); //May invokes next DNS query + + assert(this->pending > 0); + if(--pending == 0 && !this->finished) { //Order matters we have to decrease pensing! + this->callback(false, "no results"); + this->finished = true; + return; + } + } + + typedef std::tuple&)>> flagged_executor_t; + + flagged_executor_t execute_subsrv_ts; + std::tuple subsrv_ts; + + flagged_executor_t execute_subsrv_ts3; + std::tuple subsrv_ts3; + + flagged_executor_t execute_tsdns; + std::tuple tsdns; + + flagged_executor_t execute_subdomain; + std::tuple subdomain; + + //Only after subsrc and tsdns failed + flagged_executor_t execute_rootsrv; + std::tuple rootsrv; + + //Only after subsrc and tsdns failed + flagged_executor_t execute_rootdomain; + std::tuple rootdomain; + + #define try_answer_test(element, executor) \ + if(std::get<0>(element) == State::SUCCESS) { \ + this->callback(true, std::get<2>(element)); \ + return true; \ + } else if(std::get<0>(element) == State::PENDING) { \ + if(!std::get<0>(executor)) { \ + std::get<0>(executor) = true; \ + if(!!std::get<1>(executor)) { \ + std::get<1>(executor)(_this); \ + return false; \ + } else { \ + std::get<1>(element) = "No executor"; \ + std::get<0>(element) = State::FAILED; \ + } \ + } else \ + return false; \ + } + + bool try_answer(const std::shared_ptr& _this) { + if(this->finished) + return true; + + try_answer_test(this->subsrv_ts, this->execute_subsrv_ts); + try_answer_test(this->subsrv_ts3, this->execute_subsrv_ts3); + try_answer_test(this->tsdns, this->execute_tsdns); + try_answer_test(this->subdomain, this->execute_subdomain); + try_answer_test(this->rootsrv, this->execute_rootsrv); + try_answer_test(this->rootdomain, this->execute_rootdomain); + return false; + } +}; + +void tc::dns::cr(Resolver& resolver, const tc::dns::ServerAddress& address, const tc::dns::cr_callback_t& callback) { + auto status = std::make_shared(); + status->address = address; + status->callback = callback; + status->pending++; + + status->execute_subsrv_ts = { + false, + [&resolver](const std::shared_ptr& status) { + //std::cout << "Execute subsrc ts" << std::endl; + status->pending++; + tc::dns::cr_srv(resolver, status->address, [status](bool success, auto data) { + if(success) { + status->subsrv_ts = {CrStatus::SUCCESS, "", std::get(data)}; + } else { + status->subsrv_ts = {CrStatus::FAILED, std::get(data), {}}; + } + status->do_done(status); + }, "_ts._udp"); + } + }; + /* execute */ + std::get<0>(status->execute_subsrv_ts) = true; + + status->execute_subsrv_ts3 = { + false, + [&resolver](const std::shared_ptr& status) { + //std::cout << "Execute subsrc ts3" << std::endl; + status->pending++; + tc::dns::cr_srv(resolver, status->address, [status](bool success, auto data) { + if(success) { + status->subsrv_ts3 = {CrStatus::SUCCESS, "", std::get(data)}; + } else { + status->subsrv_ts3 = {CrStatus::FAILED, std::get(data), {}}; + } + status->do_done(status); + }, "_ts3._udp"); + } + }; + /* execute */ + std::get<0>(status->execute_subsrv_ts3) = true; + + status->execute_subdomain = { + false, + [&resolver](const std::shared_ptr& status) { + //std::cout << "Execute subdomain" << std::endl; + status->pending++; + tc::dns::cr_ip(resolver, status->address, [status](bool success, auto data) { + if(success) { + status->subdomain = {CrStatus::SUCCESS, "", std::get(data)}; + } else { + status->subdomain = {CrStatus::FAILED, std::get(data), {}}; + } + status->do_done(status); + }); + } + }; + /* execute */ + //Will be autoamticall be executed after the SRV stuff + //std::get<0>(status->execute_subdomain) = true; + + status->execute_tsdns = { + false, + [&resolver](const std::shared_ptr& status) { + //std::cout << "Execute tsdns" << std::endl; + status->pending++; + + tc::dns::cr_tsdns(resolver, status->address, [status](bool success, auto data) { + if(success) { + status->tsdns = {CrStatus::SUCCESS, "", std::get(data)}; + } else { + status->tsdns = {CrStatus::FAILED, std::get(data), {}}; + } + status->do_done(status); + }); + } + }; + /* execute */ + //Execute the TSDNS request right at the beginning because it could hang sometimes + std::get<0>(status->execute_tsdns) = true; + + auto root_domain = tc::dns::domain_root(status->address.host); + if(root_domain != status->address.host) { + status->execute_rootsrv = { + false, + [&resolver](const std::shared_ptr& status) { + //std::cout << "Execute root srv" << std::endl; + status->pending++; + + tc::dns::cr_srv(resolver, { + tc::dns::domain_root(status->address.host), + status->address.port + }, [status](bool success, auto data) { + if(success) { + status->rootsrv = {CrStatus::SUCCESS, "", std::get(data)}; + } else { + status->rootsrv = {CrStatus::FAILED, std::get(data), {}}; + } + status->do_done(status); + }, "_ts3._udp"); + } + }; + + status->execute_rootdomain = { + false, + [&resolver](const std::shared_ptr& status) { + //std::cout << "Execute root domain" << std::endl; + status->pending++; + + tc::dns::cr_ip(resolver,{ + tc::dns::domain_root(status->address.host), + status->address.port + }, [status](bool success, auto data) { + if(success) { + status->rootdomain = {CrStatus::SUCCESS, "", std::get(data)}; + } else { + status->rootdomain = {CrStatus::FAILED, std::get(data), {}}; + } + status->do_done(status); + }); + } + }; + } + + /* Only execute after every executor has been registered! */ + std::get<1>(status->execute_subsrv_ts)(status); + std::get<1>(status->execute_subsrv_ts3)(status); + //std::get<1>(status->execute_subdomain)(status); + std::get<1>(status->execute_tsdns)(status); + + status->do_done(status); +} \ No newline at end of file diff --git a/native/dns/utils.h b/native/dns/utils.h new file mode 100644 index 0000000..5efb48f --- /dev/null +++ b/native/dns/utils.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include "./src/resolver.h" + +namespace tc::dns { + struct ServerAddress { + std::string host; + uint16_t port; + }; + typedef std::function& /* data */)> cr_callback_t; + + inline std::string domain_root(std::string input) { + size_t dot_count = 0; + auto it = input.rbegin(); + while(it != input.rend()) { + if(*it == '.' && ++dot_count == 2) + break; + it++; + } + std::string result{}; + result.append(input.begin() + std::distance(it, input.rend()), input.end()); + return result; + } + + extern void cr_ip(Resolver& resolver, const ServerAddress& address, const cr_callback_t& callback); + extern void cr_srv(Resolver& resolver, const ServerAddress& address, const cr_callback_t& callback, const std::string& application = "_ts._udp"); + extern void cr_tsdns(Resolver& resolver, const ServerAddress& address, const cr_callback_t& callback); + extern void cr(Resolver& resolver, const tc::dns::ServerAddress& address, const tc::dns::cr_callback_t& callback); +} \ No newline at end of file diff --git a/native/serverconnection/CMakeLists.txt b/native/serverconnection/CMakeLists.txt index 1eb82d3..32f3f8c 100644 --- a/native/serverconnection/CMakeLists.txt +++ b/native/serverconnection/CMakeLists.txt @@ -58,6 +58,7 @@ endif() add_nodejs_module(${MODULE_NAME} ${SOURCE_FILES} ${NODEJS_SOURCE_FILES}) target_link_libraries(${MODULE_NAME} ${NODEJS_LIBRARIES}) +#target_compile_options(${MODULE_NAME} PUBLIC "-fPIC") find_package(TomMath REQUIRED) include_directories(${TomMath_INCLUDE_DIR}) @@ -132,7 +133,7 @@ else() set(REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} libstdc++fs.a asound - jack + jack.a pthread ) endif() diff --git a/native/serverconnection/src/audio/AudioInput.cpp b/native/serverconnection/src/audio/AudioInput.cpp index 3eafe2d..247fd33 100644 --- a/native/serverconnection/src/audio/AudioInput.cpp +++ b/native/serverconnection/src/audio/AudioInput.cpp @@ -101,15 +101,16 @@ bool AudioInput::recording() { void AudioInput::stop() { lock_guard lock(this->input_stream_lock); - if(this->input_stream) - Pa_StopStream(this->input_stream); + if(this->input_stream) { + Pa_AbortStream(this->input_stream); + } } void AudioInput::close_device() { lock_guard lock(this->input_stream_lock); if(this->input_stream) { /* TODO: Test for errors */ - Pa_StopStream(this->input_stream); + Pa_AbortStream(this->input_stream); Pa_CloseStream(this->input_stream); this->input_stream = nullptr; diff --git a/native/serverconnection/src/connection/ProtocolHandler.h b/native/serverconnection/src/connection/ProtocolHandler.h index d775133..b53507c 100644 --- a/native/serverconnection/src/connection/ProtocolHandler.h +++ b/native/serverconnection/src/connection/ProtocolHandler.h @@ -99,7 +99,7 @@ namespace tc { struct { pow_state::value state; - uint32_t client_build_timestamp = 173265950 /* TS3 */; /* needs to be lower than 173265950 for old stuff, else new protocol */ + uint32_t client_ts3_build_timestamp = 173265950 /* TS3 */; /* needs to be lower than 173265950 for old stuff, else new protocol */ uint8_t client_control_data[4] = {0,0,0,0}; uint8_t server_control_data[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t server_data[100]; diff --git a/native/serverconnection/src/connection/ProtocolHandlerPOW.cpp b/native/serverconnection/src/connection/ProtocolHandlerPOW.cpp index abac31e..3679673 100644 --- a/native/serverconnection/src/connection/ProtocolHandlerPOW.cpp +++ b/native/serverconnection/src/connection/ProtocolHandlerPOW.cpp @@ -2,6 +2,7 @@ #include "ServerConnection.h" #include "Socket.h" #include "../logger.h" +#include "Error.h" #include #include @@ -61,18 +62,30 @@ void ProtocolHandler::handlePacketInit(const std::shared_ptracknowledge_handler.reset(); /* we don't need an ack anymore for our init packet */ if(packet_state == pow_state::COOKIE_SET) { - if(data.length() != 21) + if(data.length() != 21 && data.length() != 5) { + log_trace(category::connection, tr("[POW] Dropping cookie packet (got {} bytes expect 21 or 5 bytes)"), data.length()); return; + } - memcpy(&this->pow.server_control_data[0], &data[1], 16); - //TODO test client data reserved bytes + if(data.length() == 21) { + memcpy(&this->pow.server_control_data[0], &data[1], 16); + //TODO test client data reserved bytes + } else { + auto errc = ntohl(*(uint32_t*) &data[1]); + auto err = ts::findError(errc); + + log_error(category::connection, tr("[POW] Received error code: {:x} ({})"), errc, err.message); + this->handle->call_connect_result.call(this->handle->errors.register_error(tr("received error: ") + to_string(errc) + " (" + err.message + ")"), true); + this->handle->close_connection(); + return; + } /* send puzzle get request */ { this->pow.state = pow_state::PUZZLE_SET; /* next expected packet state */ uint8_t response_buffer[25]; - le2be32(this->pow.client_build_timestamp, &response_buffer[0]); + le2be32(this->pow.client_ts3_build_timestamp, &response_buffer[0]); response_buffer[4] = pow_state::PUZZLE_GET; memcpy(&response_buffer[5], this->pow.server_control_data, 16); @@ -86,8 +99,11 @@ void ProtocolHandler::handlePacketInit(const std::shared_ptrpow.client_build_timestamp, &response_buffer[0]); + le2be32(this->pow.client_ts3_build_timestamp, &response_buffer[0]); response_buffer[4] = pow_state::PUZZLE_SOLVE; memcpy(&response_buffer[5], &data[1], 64 * 2 + 100 + 4); @@ -132,13 +148,15 @@ void ProtocolHandler::handlePacketInit(const std::shared_ptrpow.state = pow_state::COOKIE_SET; /* next expected packet state */ - if((this->pow.client_control_data[0] & 0x01) == 0) { + if((this->pow.client_control_data[0] & 0x01U) == 0) { generate_random(this->pow.client_control_data, 4); - this->pow.client_control_data[0] |= 0x01; + this->pow.client_control_data[0] |= 0x01U; } + this->pow.client_ts3_build_timestamp = floor < seconds > ((system_clock::now() - hours{24}).time_since_epoch()).count(); + uint8_t response_buffer[21]; - le2be32(this->pow.client_build_timestamp, &response_buffer[0]); + le2be32(this->pow.client_ts3_build_timestamp, &response_buffer[0]); response_buffer[4] = pow_state::COOKIE_GET; memset(&response_buffer[5], 0, 4); memcpy(&response_buffer[9], &this->pow.client_control_data, 4); diff --git a/native/serverconnection/test/js/flood.ts b/native/serverconnection/test/js/flood.ts index 74bb43f..d3c3d87 100644 --- a/native/serverconnection/test/js/flood.ts +++ b/native/serverconnection/test/js/flood.ts @@ -11,7 +11,7 @@ import {NativeServerConnection} from "teaclient_connection"; //remote_host: "54.36.232.11", /* the beast */ //remote_host: "79.133.54.207", /* gommehd.net */ -const target_address = "localhost"; +const target_address = "51.68.181.92"; const { host, port } = { host: target_address.split(":")[0], port: target_address.split(":").length > 1 ? parseInt(target_address.split(":")[1]) : 9987 @@ -117,30 +117,8 @@ class Bot { const bot_list = []; -for(let index = 0; index < 20; index++) { +for(let index = 0; index < 1; index++) { const bot = new Bot(); bot_list.push(bot); bot.connect(); -} - -/* -import * as net from "net"; -import * as tls from "tls"; -import * as https from "https"; -process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; - -const run = async () => { - try { - console.log("request"); - const response = await new Promise((resolve, reject) => { - https.get("https://localhost:30303", resolve).on('error', reject); - }); - - console.log("done"); - //console.log("response: %o", response); - } catch(error) { - console.log("error: %o", error); - } -}; -setInterval(run, 10); -*/ \ No newline at end of file +} \ No newline at end of file From c4593fe6c210c8c9dd0d7effac468c7cabb7307e Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 24 Oct 2019 20:01:25 +0200 Subject: [PATCH 3/3] Updated CMake file for unbound --- github | 2 +- native/dns/CMakeLists.txt | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/github b/github index a1490ab..14645dc 160000 --- a/github +++ b/github @@ -1 +1 @@ -Subproject commit a1490ab0d97f65e27845fec6daf88be8ed6d1014 +Subproject commit 14645dca78396c915ad4ad122d532f24fdfd2969 diff --git a/native/dns/CMakeLists.txt b/native/dns/CMakeLists.txt index eae7454..0f6ec25 100644 --- a/native/dns/CMakeLists.txt +++ b/native/dns/CMakeLists.txt @@ -4,16 +4,14 @@ set(SOURCE_FILES ${SOURCE_FILES} src/resolver.cpp src/types.cpp src/response.cpp find_package(Libevent REQUIRED) include_directories(${LIBEVENT_INCLUDE_DIRS}) -message("EVENT: ${LIBEVENT_INCLUDE_DIRS}") -#find_package(Unbound REQUIRED) -#include_directories(${Unbound_INCLUDE_DIR}) +find_package(unbound REQUIRED) add_nodejs_module(${MODULE_NAME} binding.cc ${SOURCE_FILES}) -target_link_libraries(${MODULE_NAME} /home/wolverindev/TeaSpeak-Client/third_party/unbound/build/lib/libunbound.a ${LIBEVENT_STATIC_LIBRARIES} pthread) +target_link_libraries(${MODULE_NAME} unbound::static ${LIBEVENT_STATIC_LIBRARIES} pthread) add_executable(DNS-Test ${SOURCE_FILES} test/main.cpp) -target_link_libraries(DNS-Test /home/wolverindev/TeaSpeak-Client/third_party/unbound/build/lib/libunbound.a ssl crypto ${LIBEVENT_STATIC_LIBRARIES} pthread) +target_link_libraries(DNS-Test unbound::static ssl crypto ${LIBEVENT_STATIC_LIBRARIES} pthread) add_executable(DNS-Test2 test/crash.cpp) -target_link_libraries(DNS-Test2 /home/wolverindev/TeaSpeak-Client/third_party/unbound/build/lib/libunbound.a ssl crypto ${LIBEVENT_STATIC_LIBRARIES} pthread) \ No newline at end of file +target_link_libraries(DNS-Test2 unbound::static ssl crypto ${LIBEVENT_STATIC_LIBRARIES} pthread) \ No newline at end of file