what have i done?

main
Brett 2023-10-27 02:18:05 -04:00
parent 6e0419ecd0
commit a16cd8619d
5 changed files with 259 additions and 153 deletions

View File

@ -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

View File

@ -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 <insane_dns/constants.h>
#include <type_traits>
#include <asio.hpp>
#include <cstdint>
#include <functional>
#include <blt/std/memory.h>
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
@ -110,6 +115,70 @@ namespace blt
}
};
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<unsigned char>& 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<unsigned char>& 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<class T>
// struct is_byte_ptr_type : std::integral_constant<bool,
// std::is_same_v<T, std::int8_t*> || std::is_same_v<T, std::uint8_t*> ||
// std::is_same_v<T, unsigned char*> || std::is_same_v<T, char*> || std::is_same_v<T, signed char*>>
// {
// };
//
// template<typename T>
// inline constexpr bool is_byte_ptr_type_v = is_byte_ptr_type<T>::value;
//
// template<typename IN, typename OUT>
// using messageFunction = std::function<void(const std::string&, const IN&, size_t, OUT&, size_t&)>;

@ -1 +1 @@
Subproject commit 16ba4ed192f877d1ea6c9e81c22cb2b04b2014f5
Subproject commit 55c497475e15b9a4ff0ac319c2997bd1869c4454

View File

@ -6,11 +6,10 @@
#define INSANE_DNS_IP_H
#include <blt/std/string.h>
#include <blt/compatibility.h>
#include <blt/std/assert.h>
#include <asio.hpp>
using asio::ip::udp;
using asio::ip::tcp;
#include <asio.hpp>
struct IPAddress
{
@ -34,33 +33,68 @@ struct IPAddress
}
};
template<typename IN, typename OUT>
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);
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;
};
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);
// using a template instead of a macro. Are you proud of me dad?
template<typename RET, typename V4, typename V6>
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<typename IN, typename OUT>
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<asio::ip::udp::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<asio::ip::tcp::endpoint>(address, v4, v6);
}
struct request_info
{
size_t number_of_bytes;
size_t number_of_answers;
};
#endif //INSANE_DNS_IP_H

View File

@ -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<std::string> 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<std::string> 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<answer>& answers)
}
}
template<typename T, typename INFO, typename MESSENGER>
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<typename T, typename INFO>
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<answer> 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<unsigned char, 65535> recv_buf{};
std::array<unsigned char, 65535> mod_recv_buf{};
blt::scoped_buffer<unsigned char> input_recv_buffer{PACKET_BUFFER_SIZE};
blt::scoped_buffer<unsigned char> 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<answer> 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<unsigned char, 65535> recv_buf{};
std::array<unsigned char, 65535> mod_recv_buf{};
blt::scoped_buffer<unsigned char> input_recv_buffer{PACKET_BUFFER_SIZE};
blt::scoped_buffer<unsigned char> 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<answer> 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)