From a16cd8619d264472f444d59e4383bdc1d269e19e Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 27 Oct 2023 02:18:05 -0400 Subject: [PATCH] what have i done? --- include/insane_dns/constants.h | 34 +++++- include/insane_dns/util.h | 89 +++++++++++++-- libraries/BLT | 2 +- src/ip.h | 88 ++++++++++----- src/main.cpp | 199 ++++++++++++++------------------- 5 files changed, 259 insertions(+), 153 deletions(-) diff --git a/include/insane_dns/constants.h b/include/insane_dns/constants.h index 9654404..19fd4a2 100644 --- a/include/insane_dns/constants.h +++ b/include/insane_dns/constants.h @@ -1,6 +1,6 @@ /* * Global constants file. - * Copyright (C) 2023 Brett Terpstra + * Copyright (C) 2023 Brett Terpstra, Et al * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,35 @@ * | Magic Number Constants | * -------------------------------------------- */ -/** DNS header data offset. This is the first byte that isn't a header value (should be question label length octet) */ -static constexpr size_t DNS_HEADER_END = 12; + +static constexpr size_t PACKET_BUFFER_SIZE = 65535; + +/* + * These structs are used in combination with templates to create a form of polymorphism which has significantly reduced the code reuse. + * The data set within these structures are specific to the protocol they implement. UDP is exactly as specified in the RFC spec + * TCP is what I observed from wireshark (+2 byte offset) + */ +struct DNS_UDP_INFO_t +{ + /** DNS header data offset. This is the first byte that isn't a header value (should be question label length octet) */ + size_t HEADER_END = 12; + /** DNS questions count data offset. In the standard header this is 4 bytes in */ + size_t QUESTIONS_BEGIN = 4; + /** DNS answer count data offset. */ + size_t ANSWERS_BEGIN = 6; +}; + +/* + * The TCP version appears to use a 2 byte unsigned short to represent length. This struct accounts for that. + */ +struct DNS_TCP_INFO_t +{ + size_t HEADER_END = 14; + size_t QUESTIONS_BEGIN = 6; + size_t ANSWERS_BEGIN = 8; +}; + +static constexpr DNS_UDP_INFO_t DNS_UDP_INFO; +static constexpr DNS_TCP_INFO_t DNS_TCP_INFO; #endif //INSANE_DNS_CONSTANTS_H diff --git a/include/insane_dns/util.h b/include/insane_dns/util.h index 5929f99..e5a664b 100644 --- a/include/insane_dns/util.h +++ b/include/insane_dns/util.h @@ -1,7 +1,7 @@ /* - * BLT Memory Util for parsing DNS packets. + * BLT Memory Util for parsing DNS packets / BLT + ASIO simple packet sender. * This software is unlikely to become part of BLT main but is provided under the BLT license (GPL 3). - * Copyright (C) 2023 Brett Terpstra + * Copyright (C) 2023 Brett Terpstra, Et al * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,14 @@ #define INSANE_DNS_UTIL_H #include +#include +#include +#include +#include +#include + +using asio::ip::udp; +using asio::ip::tcp; namespace blt { @@ -28,20 +36,17 @@ namespace blt /** * Basic parser for processing strings of bytes received from UDP sockets. This class provides a simple interface for efficiently copying the * big endian bytes from the network into the little endian bytes required by x86 processors. The class also provides basic safety guarantees - * in that the program will halt execution if reading the bytes would overflow the internal buffer. + * in the sense that the program will halt execution if reading the bytes would overflow the internal buffer. */ class byte_reader { private: unsigned char* _data; const size_t _size; - mutable size_t _current_byte = DNS_HEADER_END; + mutable size_t _current_byte; public: - explicit byte_reader(unsigned char* data, size_t size, bool tcp = false): _data(data), _size(size) - { - if (tcp) - _current_byte += 2; - } + explicit byte_reader(unsigned char* data, size_t size, size_t start_offset): _data(data), _size(size), _current_byte(start_offset) + {} /** * Read the next byte in the data stream then increment the internal counter by 1 @@ -109,7 +114,71 @@ namespace blt return &_data[_current_byte]; } }; - + + namespace network + { + /** + * Sends a simple UDP message to the provided host on the DNS port and outputs the response into the out buffer + * @param host host to send the message to + * @param in buffer to send + * @param in_size size of buffer + * @param out buffer to write to + * @param out_size size of data that was written. Make sure `out` has enough room for the packet. + */ + void sendUDPMessage(const std::string& host, const unsigned char*& in, size_t in_size, blt::scoped_buffer& out, + size_t& out_size) + { + asio::io_context io_context; + udp::endpoint receiver_endpoint(asio::ip::address::from_string(host), 53); + + udp::socket socket(io_context); + socket.open(udp::v4()); + + socket.send_to(asio::const_buffers_1(in, in_size), receiver_endpoint); + + udp::endpoint sender_endpoint; + out_size = socket.receive_from(asio::buffer(out.data(), out.size()), sender_endpoint); + } + + /** + * Sends a simple TCP message to the provided host on the DNS port and outputs the response into the out buffer + * @param host host to send the message to + * @param in buffer to send + * @param in_size size of buffer + * @param out buffer to write to + * @param out_size size of data that was written. Make sure `out` has enough room for the packet. + */ + void sendTCPMessage(const std::string& host, const unsigned char*& in, size_t in_size, blt::scoped_buffer& out, + size_t& out_size) + { + asio::io_context io_context; + tcp::resolver resolver(io_context); + tcp::resolver::results_type endpoints = resolver.resolve(host, "53"); + + tcp::socket socket(io_context); + asio::connect(socket, endpoints); + + asio::write(socket, asio::buffer(in, in_size)); + + out_size = socket.read_some(asio::buffer(out.data(), out.size())); + } + } + } #endif //INSANE_DNS_UTIL_H + +// these are no longer used, but I am keeping them around because they are useful and were not added to git + +// template +// struct is_byte_ptr_type : std::integral_constant || std::is_same_v || +// std::is_same_v || std::is_same_v || std::is_same_v> +// { +// }; +// +// template +// inline constexpr bool is_byte_ptr_type_v = is_byte_ptr_type::value; +// +// template +// using messageFunction = std::function; diff --git a/libraries/BLT b/libraries/BLT index 16ba4ed..55c4974 160000 --- a/libraries/BLT +++ b/libraries/BLT @@ -1 +1 @@ -Subproject commit 16ba4ed192f877d1ea6c9e81c22cb2b04b2014f5 +Subproject commit 55c497475e15b9a4ff0ac319c2997bd1869c4454 diff --git a/src/ip.h b/src/ip.h index e061305..ed6e3c4 100644 --- a/src/ip.h +++ b/src/ip.h @@ -6,11 +6,10 @@ #define INSANE_DNS_IP_H #include +#include #include -#include -using asio::ip::udp; -using asio::ip::tcp; +#include struct IPAddress { @@ -34,33 +33,68 @@ struct IPAddress } }; -template -void sendUDPMessage(const std::string& host, const IN& in, size_t in_size, OUT& out, size_t& out_size) +struct bind_address { - asio::io_context io_context; - udp::endpoint receiver_endpoint(asio::ip::address::from_string(host), 53); - - udp::socket socket(io_context); - socket.open(udp::v4()); - - socket.send_to(asio::const_buffers_1(in, in_size), receiver_endpoint); - - udp::endpoint sender_endpoint; - out_size = socket.receive_from(asio::buffer(out), sender_endpoint); + unsigned short int port; + bool use_v4 = true; + bool use_v6 = true; + // if these are empty it will use ASIO default + std::string IP_V4; + std::string IP_V6; +}; + +// using a template instead of a macro. Are you proud of me dad? +template +inline RET BLT_INTERNAL_ENDPOINT(const bind_address& address, V4 v4, V6 v6) +{ + // this is an ugly mess. Ignore it. + BLT_ASSERT(address.use_v4 || address.use_v6); + if (address.use_v6) + { + if (address.IP_V6.empty()) + return {v6, address.port}; + else + return {asio::ip::address::from_string(address.IP_V6), address.port}; + } + if (address.use_v4) + { + if (address.IP_V4.empty()) + return {v4, address.port}; + else + return {asio::ip::address::from_string(address.IP_V4), address.port}; + } + // suppress compiler warnings. Also, could do std::exit() but this is more fun :3 + return {asio::ip::address::from_string("Unreachable Code"), 42069}; } -template -void sendTCPMessage(const std::string& host, const IN& in, size_t in_size, OUT& out, size_t& out_size){ - asio::io_context io_context; - tcp::resolver resolver(io_context); - tcp::resolver::results_type endpoints = resolver.resolve(host, "53"); - - tcp::socket socket(io_context); - asio::connect(socket, endpoints); - - asio::write(socket, asio::buffer(in, in_size)); - - out_size = socket.read_some(asio::buffer(out)); +/** + * Convert a bind_address into a ASIO UDP endpoint + * @param address address to convert + * @return ASIO UDP endpoint + */ +inline asio::ip::udp::endpoint toUDPEndpoint(const bind_address& address) +{ + auto v4 = asio::ip::udp::v4(); + auto v6 = asio::ip::udp::v6(); + return BLT_INTERNAL_ENDPOINT(address, v4, v6); } +/** + * Convert a bind_address into a ASIO TCP endpoint + * @param address address to convert + * @return ASIO TCP endpoint + */ +inline asio::ip::tcp::endpoint toTCPEndpoint(const bind_address& address) +{ + auto v4 = asio::ip::tcp::v4(); + auto v6 = asio::ip::tcp::v6(); + return BLT_INTERNAL_ENDPOINT(address, v4, v6); +} + +struct request_info +{ + size_t number_of_bytes; + size_t number_of_answers; +}; + #endif //INSANE_DNS_IP_H diff --git a/src/main.cpp b/src/main.cpp index 11c787b..38f589a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,12 @@ /** What port to run the server on */ static constexpr unsigned short int SERVER_PORT = 5555; +/** + * How should we bind to the local machine? By default this will use IPv4 and IPv6 with the default ASIO endpoint. + * Only change this if you really need to and know what you are doing. + */ +static constexpr bind_address address{SERVER_PORT}; + /** should we strictly match results? ie block `*wikipedia.org*` or just `wikipedia.org`? */ static constexpr bool STRICT_MATCHING = false; @@ -60,13 +66,14 @@ static const std::unordered_set DISALLOWED_DOMAINS{ */ // these features were planned but not added because I realized you guys won't care or give extra marks which broke my obsession with it // so uhh don't change em otherwise the code will break :3 +// as it is im still adding new features and stuff (TCP) and messing with the code trying to get it cleaner /** true -> only match A records ; false -> match any named record (configure with NON_STRICT_REPLACE_ALL) */ static constexpr bool STRICT_FILTERING = true; /** true -> match all records ; false -> match only records we might want to replace (A, AAAA, CNAME) */ static constexpr bool NON_STRICT_REPLACE_ALL = true; -// was going to add TCP and ad blocking support +// was going to add ~~TCP~~ (this is now a thing for full DIG support. "can you dig it, sucka!?") and ad blocking support // that also isn't going to happen now. /** list of web address to download the ad block lists from */ static BLT_CPP20_CONSTEXPR std::vector BLOCK_LISTS{}; @@ -107,8 +114,8 @@ class question friend send_buffer; private: std::string domain; - uint16_t QTYPE; - uint16_t QCLASS; + uint16_t QTYPE = 0; + uint16_t QCLASS = 0; public: explicit question(const blt::byte_reader& reader) { @@ -327,75 +334,89 @@ void process_answers(std::vector& answers) } } +template +request_info handle_forward_request(MESSENGER messenger, const INFO& info, const T& input_recv_buffer, size_t bytes, T& forward_recv_buffer) +{ + // get the number of questions + uint16_t questions; // yes I made this part of my library just for this :3 + blt::mem::fromBytes(&input_recv_buffer[info.QUESTIONS_BEGIN], questions); // i hate little endian + + BLT_INFO("Bytes received: %d with %d questions", bytes, questions); + + // forward to google. + size_t out_bytes; + auto data = input_recv_buffer.data(); + messenger(DNS_SERVER_IP(), data, bytes, forward_recv_buffer, out_bytes); + + uint16_t num_of_answers; + blt::mem::fromBytes(&forward_recv_buffer[info.ANSWERS_BEGIN], num_of_answers); + + return {out_bytes, num_of_answers}; +} + +template +void handle_response(const INFO& info, send_buffer& return_send_buffer, request_info rq_info, T& forward_recv_buffer) +{ + blt::byte_reader reader(forward_recv_buffer.data(), forward_recv_buffer.size(), info.HEADER_END); + + BLT_INFO("Bytes answered: %d with %d answers", rq_info.number_of_bytes, rq_info.number_of_answers); + + // no one actually does multiple questions. trying to do it in dig is not easy + // and the standard isn't really designed for this (how do we handle if one question errors but the other doesn't? there is only + // one return code.) + question q(reader); + std::vector answers; + for (size_t i = 0; i < rq_info.number_of_answers; i++) + { + answer a(reader); + answers.push_back(std::move(a)); + } + + BLT_INFO("DOMAIN: %s", q().c_str()); + if (STRICT_MATCHING && BLT_CONTAINS(DISALLOWED_DOMAINS, q())) + process_answers(answers); + else if (!STRICT_MATCHING) + { + // linear search the domains for contains. Maybe find a better way to do this. + for (const auto& v : DISALLOWED_DOMAINS) + if (blt::string::contains(q(), v)) + process_answers(answers); + } + + return_send_buffer.write(forward_recv_buffer.data(), info.HEADER_END); + auto question_offset = return_send_buffer.size(); + return_send_buffer.write(q); + for (const answer& a : answers) + { + BLT_TRACE("Writing answer with type of %d", a.type()); + a.reset(question_offset); + return_send_buffer.write(a); + } + return_send_buffer.write(reader.from(), rq_info.number_of_bytes - reader.last()); +} + void run_udp_server() { try { asio::io_context io_context; - //udp::socket socket(io_context, udp::endpoint(udp::v6(), 5555)); - udp::socket socket(io_context, udp::endpoint(asio::ip::address::from_string("::1"), SERVER_PORT)); + udp::socket socket(io_context, toUDPEndpoint(address)); - std::array recv_buf{}; - std::array mod_recv_buf{}; + blt::scoped_buffer input_recv_buffer{PACKET_BUFFER_SIZE}; + blt::scoped_buffer forward_recv_buffer{PACKET_BUFFER_SIZE}; while (true) { udp::endpoint remote_endpoint; - size_t bytes = socket.receive_from(asio::buffer(recv_buf), remote_endpoint); + size_t bytes = socket.receive_from(asio::buffer(input_recv_buffer.data(), input_recv_buffer.size()), remote_endpoint); - // get the number of questions - uint16_t questions; // yes I made this part of my library just for this :3 - blt::mem::fromBytes(&recv_buf[4], questions); // i hate little endian + auto rq_info = handle_forward_request(blt::network::sendUDPMessage, DNS_UDP_INFO, input_recv_buffer, bytes, forward_recv_buffer); - BLT_INFO("Bytes received: %d with %d questions", bytes, questions); - - // forward to google. - size_t out_bytes; - sendUDPMessage(DNS_SERVER_IP(), recv_buf.data(), bytes, mod_recv_buf, out_bytes); - - uint16_t num_of_answers; - blt::mem::fromBytes(&mod_recv_buf[6], num_of_answers); - - blt::byte_reader reader(mod_recv_buf.data(), mod_recv_buf.size()); - - BLT_INFO("Bytes answered: %d with %d answers", out_bytes, num_of_answers); - - // no one actually does multiple questions. trying to do it in dig is not easy - // and the standard isn't really designed for this (how do we handle if one question errors but the other doesn't? there is only - // one return code.) - question q(reader); - std::vector answers; - for (int i = 0; i < num_of_answers; i++) - { - answer a(reader); - answers.push_back(std::move(a)); - } - - BLT_INFO("DOMAIN: %s", q().c_str()); - if (STRICT_MATCHING && BLT_CONTAINS(DISALLOWED_DOMAINS, q())) - process_answers(answers); - else if (!STRICT_MATCHING) - { - // linear search the domains for contains. Maybe find a better way to do this. - for (const auto& v : DISALLOWED_DOMAINS) - if (blt::string::contains(q(), v)) - process_answers(answers); - } - - send_buffer send; - send.write(mod_recv_buf.data(), 12); - auto question_offset = send.size(); - send.write(q); - for (const answer& a : answers) - { - BLT_TRACE("Writing answer with type of %d", a.type()); - a.reset(question_offset); - send.write(a); - } - send.write(reader.from(), out_bytes - reader.last()); + send_buffer return_send_buffer; + handle_response(DNS_UDP_INFO, return_send_buffer, rq_info, forward_recv_buffer); asio::error_code ignored_error; - socket.send_to(send.buffer(), remote_endpoint, 0, ignored_error); + socket.send_to(return_send_buffer.buffer(), remote_endpoint, 0, ignored_error); } } catch (std::exception& e) @@ -410,74 +431,28 @@ void run_tcp_server() { asio::io_context io_context; - tcp::acceptor acceptor(io_context, tcp::endpoint(asio::ip::address::from_string("::1"), SERVER_PORT)); + tcp::acceptor acceptor(io_context, toTCPEndpoint(address)); - std::array recv_buf{}; - std::array mod_recv_buf{}; + blt::scoped_buffer input_recv_buffer{PACKET_BUFFER_SIZE}; + blt::scoped_buffer forward_recv_buffer{PACKET_BUFFER_SIZE}; while (true) { tcp::socket socket(io_context); acceptor.accept(socket); asio::error_code error; - size_t bytes = socket.read_some(asio::buffer(recv_buf), error); + size_t bytes = socket.read_some(asio::buffer(input_recv_buffer.data(), input_recv_buffer.size()), error); if (error == asio::error::eof) break; else if (error) throw asio::system_error(error); - // get the number of questions - uint16_t questions; // yes I made this part of my library just for this :3 - blt::mem::fromBytes(&recv_buf[6], questions); // i hate little endian + auto rq_info = handle_forward_request(blt::network::sendTCPMessage, DNS_TCP_INFO, input_recv_buffer, bytes, forward_recv_buffer); - BLT_INFO("TCP Bytes received: %d with %d questions", bytes, questions); + send_buffer return_send_buffer; + handle_response(DNS_TCP_INFO, return_send_buffer, rq_info, forward_recv_buffer); - // forward to google. - size_t out_bytes; - sendTCPMessage(DNS_SERVER_IP(), recv_buf.data(), bytes, mod_recv_buf, out_bytes); - - uint16_t num_of_answers; - blt::mem::fromBytes(&mod_recv_buf[8], num_of_answers); - - blt::byte_reader reader(mod_recv_buf.data(), mod_recv_buf.size(), true); - - BLT_INFO("TCP Bytes answered: %d with %d answers", out_bytes, num_of_answers); - - // no one actually does multiple questions. trying to do it in dig is not easy - // and the standard isn't really designed for this (how do we handle if one question errors but the other doesn't? there is only - // one return code.) - question q(reader); - std::vector answers; - for (int i = 0; i < num_of_answers; i++) - { - answer a(reader); - answers.push_back(std::move(a)); - } - - BLT_INFO("TCP DOMAIN: %s", q().c_str()); - if (STRICT_MATCHING && BLT_CONTAINS(DISALLOWED_DOMAINS, q())) - process_answers(answers); - else if (!STRICT_MATCHING) - { - // linear search the domains for contains. Maybe find a better way to do this. - for (const auto& v : DISALLOWED_DOMAINS) - if (blt::string::contains(q(), v)) - process_answers(answers); - } - - send_buffer send; - send.write(mod_recv_buf.data(), 12); - auto question_offset = send.size(); - send.write(q); - for (const answer& a : answers) - { - BLT_TRACE("TCP Writing answer with type of %d", a.type()); - a.reset(question_offset); - send.write(a); - } - send.write(reader.from(), out_bytes - reader.last()); - - asio::write(socket, send.buffer()); + asio::write(socket, return_send_buffer.buffer()); } } catch (std::exception& e)