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/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
diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt
index 4b90b83..f78f7de 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..0f6ec25
--- /dev/null
+++ b/native/dns/CMakeLists.txt
@@ -0,0 +1,17 @@
+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})
+
+find_package(unbound REQUIRED)
+
+add_nodejs_module(${MODULE_NAME} binding.cc ${SOURCE_FILES})
+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 unbound::static ssl crypto ${LIBEVENT_STATIC_LIBRARIES} pthread)
+
+add_executable(DNS-Test2 test/crash.cpp)
+target_link_libraries(DNS-Test2 unbound::static 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