what have i done?
parent
6e0419ecd0
commit
a16cd8619d
|
@ -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 |
|
||||
* --------------------------------------------
|
||||
*/
|
||||
|
||||
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) */
|
||||
static constexpr size_t DNS_HEADER_END = 12;
|
||||
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
|
||||
|
|
|
@ -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
|
86
src/ip.h
86
src/ip.h
|
@ -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
|
||||
|
|
145
src/main.cpp
145
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<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,45 +334,39 @@ void process_answers(std::vector<answer>& answers)
|
|||
}
|
||||
}
|
||||
|
||||
void run_udp_server()
|
||||
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)
|
||||
{
|
||||
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));
|
||||
|
||||
std::array<unsigned char, 65535> recv_buf{};
|
||||
std::array<unsigned char, 65535> mod_recv_buf{};
|
||||
while (true)
|
||||
{
|
||||
udp::endpoint remote_endpoint;
|
||||
size_t bytes = socket.receive_from(asio::buffer(recv_buf), 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
|
||||
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;
|
||||
sendUDPMessage(DNS_SERVER_IP(), recv_buf.data(), bytes, mod_recv_buf, 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(&mod_recv_buf[6], num_of_answers);
|
||||
blt::mem::fromBytes(&forward_recv_buffer[info.ANSWERS_BEGIN], num_of_answers);
|
||||
|
||||
blt::byte_reader reader(mod_recv_buf.data(), mod_recv_buf.size());
|
||||
return {out_bytes, num_of_answers};
|
||||
}
|
||||
|
||||
BLT_INFO("Bytes answered: %d with %d answers", 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 (int i = 0; i < num_of_answers; i++)
|
||||
for (size_t i = 0; i < rq_info.number_of_answers; i++)
|
||||
{
|
||||
answer a(reader);
|
||||
answers.push_back(std::move(a));
|
||||
|
@ -382,20 +383,40 @@ void run_udp_server()
|
|||
process_answers(answers);
|
||||
}
|
||||
|
||||
send_buffer send;
|
||||
send.write(mod_recv_buf.data(), 12);
|
||||
auto question_offset = send.size();
|
||||
send.write(q);
|
||||
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);
|
||||
send.write(a);
|
||||
return_send_buffer.write(a);
|
||||
}
|
||||
send.write(reader.from(), out_bytes - reader.last());
|
||||
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, toUDPEndpoint(address));
|
||||
|
||||
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(input_recv_buffer.data(), input_recv_buffer.size()), remote_endpoint);
|
||||
|
||||
auto rq_info = handle_forward_request(blt::network::sendUDPMessage, DNS_UDP_INFO, input_recv_buffer, bytes, forward_recv_buffer);
|
||||
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue