less copy and less lock
This commit is contained in:
@@ -24,8 +24,8 @@ public:
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
static_cast<Derived*>(this)->before_loop();
|
static_cast<Derived*>(this)->before_loop();
|
||||||
|
PktRequest<Derived>* pkt;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
PktRequest<Derived>* pkt;
|
|
||||||
queue.take(pkt);
|
queue.take(pkt);
|
||||||
static_cast<Derived*>(this)->handle_next_packet(pkt);
|
static_cast<Derived*>(this)->handle_next_packet(pkt);
|
||||||
delete pkt;
|
delete pkt;
|
||||||
|
|||||||
@@ -23,22 +23,21 @@ class PktRequest {
|
|||||||
private:
|
private:
|
||||||
FilterAction action = FilterAction::NOACTION;
|
FilterAction action = FilterAction::NOACTION;
|
||||||
mnl_socket* nl = nullptr;
|
mnl_socket* nl = nullptr;
|
||||||
nfgenmsg * nfg = nullptr;
|
uint16_t res_id;
|
||||||
nfqnl_msg_packet_hdr *ph;
|
uint32_t packet_id;
|
||||||
shared_ptr<char[]> packet_buffer; // Will be deallocated here
|
|
||||||
size_t data_size = 0;
|
|
||||||
public:
|
public:
|
||||||
const bool is_ipv6;
|
bool is_ipv6;
|
||||||
Tins::IP* ipv4 = nullptr;
|
Tins::IP* ipv4 = nullptr;
|
||||||
Tins::IPv6* ipv6 = nullptr;
|
Tins::IPv6* ipv6 = nullptr;
|
||||||
Tins::TCP* tcp = nullptr;
|
Tins::TCP* tcp = nullptr;
|
||||||
Tins::UDP* udp = nullptr;
|
Tins::UDP* udp = nullptr;
|
||||||
const L4Proto l4_proto;
|
L4Proto l4_proto;
|
||||||
const bool is_input;
|
bool is_input;
|
||||||
|
|
||||||
const string packet;
|
string packet;
|
||||||
const string data;
|
char* data;
|
||||||
const stream_id sid;
|
size_t data_size;
|
||||||
|
stream_id sid;
|
||||||
|
|
||||||
T* ctx;
|
T* ctx;
|
||||||
|
|
||||||
@@ -89,13 +88,21 @@ class PktRequest {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
PktRequest(shared_ptr<char[]> buf, Tins::IP* ipv4, const char* payload, size_t plen, stream_id sid, T* ctx, mnl_socket* nl, nfgenmsg *nfg, nfqnl_msg_packet_hdr *ph, bool is_input):
|
PktRequest(const char* payload, size_t plen, T* ctx, mnl_socket* nl, nfgenmsg *nfg, nfqnl_msg_packet_hdr *ph, bool is_input):
|
||||||
is_ipv6(false), ipv4(ipv4), packet(string(payload, plen)), sid(sid), ctx(ctx), nl(nl), nfg(nfg), ph(ph),
|
ctx(ctx), nl(nl), res_id(nfg->res_id),
|
||||||
is_input(is_input), packet_buffer(buf), l4_proto(fill_l4_info()), data(string(payload+(plen-data_size), data_size)) {}
|
packet_id(ph->packet_id), is_input(is_input),
|
||||||
|
packet(string(payload, plen)),
|
||||||
PktRequest(shared_ptr<char[]> buf, Tins::IPv6* ipv6, const char* payload, size_t plen, stream_id sid, T* ctx, mnl_socket* nl, nfgenmsg *nfg, nfqnl_msg_packet_hdr *ph, bool is_input):
|
is_ipv6((payload[0] & 0xf0) == 0x60){
|
||||||
is_ipv6(true), ipv6(ipv6), packet(string(payload, plen)), sid(sid), ctx(ctx), nl(nl), nfg(nfg), ph(ph),
|
if (is_ipv6){
|
||||||
is_input(is_input), packet_buffer(buf), l4_proto(fill_l4_info()), data(string(payload+(plen-data_size), data_size)) {}
|
ipv6 = new Tins::IPv6((uint8_t*)packet.c_str(), plen);
|
||||||
|
sid = stream_id::make_identifier(*ipv6);
|
||||||
|
}else{
|
||||||
|
ipv4 = new Tins::IP((uint8_t*)packet.c_str(), plen);
|
||||||
|
sid = stream_id::make_identifier(*ipv4);
|
||||||
|
}
|
||||||
|
l4_proto = fill_l4_info();
|
||||||
|
data = packet.data()+(plen-data_size);
|
||||||
|
}
|
||||||
|
|
||||||
void drop(){
|
void drop(){
|
||||||
if (action == FilterAction::NOACTION){
|
if (action == FilterAction::NOACTION){
|
||||||
@@ -129,26 +136,21 @@ class PktRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
~PktRequest(){
|
~PktRequest(){
|
||||||
if (ipv4 != nullptr){
|
delete ipv4;
|
||||||
delete ipv4;
|
delete ipv6;
|
||||||
}
|
|
||||||
if (ipv6 != nullptr){
|
|
||||||
delete ipv6;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void perfrom_action(){
|
void perfrom_action(){
|
||||||
char buf[MNL_SOCKET_BUFFER_SIZE];
|
char buf[MNL_SOCKET_BUFFER_SIZE];
|
||||||
struct nlmsghdr *nlh_verdict;
|
struct nlmsghdr *nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(res_id));
|
||||||
nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(nfg->res_id));
|
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case FilterAction::ACCEPT:
|
case FilterAction::ACCEPT:
|
||||||
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT );
|
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT );
|
||||||
break;
|
break;
|
||||||
case FilterAction::DROP:
|
case FilterAction::DROP:
|
||||||
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_DROP );
|
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_DROP );
|
||||||
break;
|
break;
|
||||||
case FilterAction::MANGLE:{
|
case FilterAction::MANGLE:{
|
||||||
if (is_ipv6){
|
if (is_ipv6){
|
||||||
@@ -156,7 +158,7 @@ class PktRequest {
|
|||||||
}else{
|
}else{
|
||||||
nfq_nlmsg_verdict_put_pkt(nlh_verdict, ipv4->serialize().data(), ipv4->size());
|
nfq_nlmsg_verdict_put_pkt(nlh_verdict, ipv4->serialize().data(), ipv4->size());
|
||||||
}
|
}
|
||||||
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT );
|
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -172,7 +174,6 @@ class PktRequest {
|
|||||||
struct internal_nfqueue_execution_data_tmp{
|
struct internal_nfqueue_execution_data_tmp{
|
||||||
mnl_socket* nl = nullptr;
|
mnl_socket* nl = nullptr;
|
||||||
void *data = nullptr;
|
void *data = nullptr;
|
||||||
shared_ptr<char[]> packet_buffer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t NFQUEUE_BUFFER_SIZE = 0xffff + (MNL_SOCKET_BUFFER_SIZE/2);
|
const size_t NFQUEUE_BUFFER_SIZE = 0xffff + (MNL_SOCKET_BUFFER_SIZE/2);
|
||||||
@@ -185,10 +186,11 @@ class NfQueue {
|
|||||||
mnl_socket* nl = nullptr;
|
mnl_socket* nl = nullptr;
|
||||||
unsigned int portid;
|
unsigned int portid;
|
||||||
public:
|
public:
|
||||||
|
char* queue_msg_buffer = nullptr;
|
||||||
const uint16_t queue_num;
|
const uint16_t queue_num;
|
||||||
|
|
||||||
NfQueue(u_int16_t queue_num): queue_num(queue_num) {
|
NfQueue(u_int16_t queue_num): queue_num(queue_num) {
|
||||||
|
queue_msg_buffer = new char[NFQUEUE_BUFFER_SIZE];
|
||||||
nl = mnl_socket_open(NETLINK_NETFILTER);
|
nl = mnl_socket_open(NETLINK_NETFILTER);
|
||||||
|
|
||||||
if (nl == nullptr) { throw runtime_error( "mnl_socket_open" );}
|
if (nl == nullptr) { throw runtime_error( "mnl_socket_open" );}
|
||||||
@@ -199,18 +201,16 @@ class NfQueue {
|
|||||||
}
|
}
|
||||||
portid = mnl_socket_get_portid(nl);
|
portid = mnl_socket_get_portid(nl);
|
||||||
|
|
||||||
char queue_msg_buffer[NFQUEUE_BUFFER_SIZE];
|
if (_send_config_cmd(NFQNL_CFG_CMD_BIND) < 0) {
|
||||||
|
|
||||||
if (_send_config_cmd(NFQNL_CFG_CMD_BIND, queue_msg_buffer) < 0) {
|
|
||||||
_clear();
|
_clear();
|
||||||
throw runtime_error( "mnl_socket_send" );
|
throw runtime_error( "mnl_socket_send" );
|
||||||
}
|
}
|
||||||
//TEST if BIND was successful
|
//TEST if BIND was successful
|
||||||
if (_send_config_cmd(NFQNL_CFG_CMD_NONE, queue_msg_buffer) < 0) { // SEND A NONE command to generate an error meessage
|
if (_send_config_cmd(NFQNL_CFG_CMD_NONE) < 0) { // SEND A NONE command to generate an error meessage
|
||||||
_clear();
|
_clear();
|
||||||
throw runtime_error( "mnl_socket_send" );
|
throw runtime_error( "mnl_socket_send" );
|
||||||
}
|
}
|
||||||
if (_recv_packet(queue_msg_buffer) == -1) { //RECV the error message
|
if (_recv_packet() == -1) { //RECV the error message
|
||||||
_clear();
|
_clear();
|
||||||
throw runtime_error( "mnl_socket_recvfrom" );
|
throw runtime_error( "mnl_socket_recvfrom" );
|
||||||
}
|
}
|
||||||
@@ -237,9 +237,18 @@ class NfQueue {
|
|||||||
nlh = nfq_nlmsg_put(queue_msg_buffer, NFQNL_MSG_CONFIG, queue_num);
|
nlh = nfq_nlmsg_put(queue_msg_buffer, NFQNL_MSG_CONFIG, queue_num);
|
||||||
nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
|
nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
|
||||||
|
|
||||||
|
#ifdef NFQUEUE_FAIL_OPEN
|
||||||
|
|
||||||
|
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO|NFQA_CFG_F_FAIL_OPEN));
|
||||||
|
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO|NFQA_CFG_F_FAIL_OPEN));
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO));
|
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO));
|
||||||
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO));
|
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO));
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
|
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
|
||||||
_clear();
|
_clear();
|
||||||
throw runtime_error( "mnl_socket_send" );
|
throw runtime_error( "mnl_socket_send" );
|
||||||
@@ -256,18 +265,16 @@ class NfQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handle_next_packet(D* data){
|
void handle_next_packet(D* data){
|
||||||
auto queue_msg_buffer = make_shared<char[]>(NFQUEUE_BUFFER_SIZE);
|
int ret = _recv_packet();
|
||||||
int ret = _recv_packet(queue_msg_buffer.get(), NFQUEUE_BUFFER_SIZE);
|
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
throw runtime_error( "mnl_socket_recvfrom" );
|
throw runtime_error( "mnl_socket_recvfrom" );
|
||||||
}
|
}
|
||||||
internal_nfqueue_execution_data_tmp raw_ptr = {
|
internal_nfqueue_execution_data_tmp raw_ptr = {
|
||||||
nl: nl,
|
nl: nl,
|
||||||
data: data,
|
data: data
|
||||||
packet_buffer: queue_msg_buffer
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ret = mnl_cb_run(queue_msg_buffer.get(), ret, 0, portid, _real_queue_cb, &raw_ptr);
|
ret = mnl_cb_run(queue_msg_buffer, ret, 0, portid, _real_queue_cb, &raw_ptr);
|
||||||
if (ret <= 0){
|
if (ret <= 0){
|
||||||
cerr << "[error] [NfQueue.handle_next_packet] mnl_cb_run error with: " << ret << endl;
|
cerr << "[error] [NfQueue.handle_next_packet] mnl_cb_run error with: " << ret << endl;
|
||||||
throw runtime_error( "mnl_cb_run error!" );
|
throw runtime_error( "mnl_cb_run error!" );
|
||||||
@@ -275,22 +282,12 @@ class NfQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
~NfQueue() {
|
~NfQueue() {
|
||||||
char queue_msg_buffer[NFQUEUE_BUFFER_SIZE];
|
_send_config_cmd(NFQNL_CFG_CMD_UNBIND);
|
||||||
_send_config_cmd(NFQNL_CFG_CMD_UNBIND, queue_msg_buffer);
|
|
||||||
_clear();
|
_clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
template<typename T, typename = enable_if_t<is_base_of_v<Tins::PDU, T>>>
|
|
||||||
static void inline _send_verdict(shared_ptr<char[]> raw_buf, T* packet, char *payload, uint16_t plen, nfgenmsg *nfg, nfqnl_msg_packet_hdr *ph, internal_nfqueue_execution_data_tmp* ctx, bool is_input){
|
|
||||||
handle_func(new PktRequest<D>(
|
|
||||||
raw_buf, packet, payload, plen,
|
|
||||||
stream_id::make_identifier(*packet),
|
|
||||||
(D*)ctx->data, ctx->nl, nfg, ph, is_input
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _real_queue_cb(const nlmsghdr *nlh, void *data_ptr) {
|
static int _real_queue_cb(const nlmsghdr *nlh, void *data_ptr) {
|
||||||
|
|
||||||
internal_nfqueue_execution_data_tmp* info = (internal_nfqueue_execution_data_tmp*) data_ptr;
|
internal_nfqueue_execution_data_tmp* info = (internal_nfqueue_execution_data_tmp*) data_ptr;
|
||||||
@@ -320,12 +317,10 @@ class NfQueue {
|
|||||||
struct nfgenmsg *nfg = (nfgenmsg *)mnl_nlmsg_get_payload(nlh);
|
struct nfgenmsg *nfg = (nfgenmsg *)mnl_nlmsg_get_payload(nlh);
|
||||||
|
|
||||||
bool is_input = ntohl(mnl_attr_get_u32(attr[NFQA_MARK])) & 0x1; // == 0x1337 that is odd
|
bool is_input = ntohl(mnl_attr_get_u32(attr[NFQA_MARK])) & 0x1; // == 0x1337 that is odd
|
||||||
// Check IP protocol version
|
handle_func(new PktRequest<D>(
|
||||||
if ( (payload[0] & 0xf0) == 0x40 ){
|
payload, plen, (D*)info->data, info->nl, nfg, ph, is_input
|
||||||
_send_verdict(info->packet_buffer, new Tins::IP((uint8_t*)payload, plen), payload, plen, nfg, ph, info, is_input);
|
));
|
||||||
}else{
|
|
||||||
_send_verdict(info->packet_buffer, new Tins::IPv6((uint8_t*)payload, plen), payload, plen, nfg, ph, info, is_input);
|
|
||||||
}
|
|
||||||
return MNL_CB_OK;
|
return MNL_CB_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,16 +329,17 @@ class NfQueue {
|
|||||||
mnl_socket_close(nl);
|
mnl_socket_close(nl);
|
||||||
nl = nullptr;
|
nl = nullptr;
|
||||||
}
|
}
|
||||||
|
delete[] queue_msg_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ssize_t _send_config_cmd(nfqnl_msg_config_cmds cmd, char* buf){
|
inline ssize_t _send_config_cmd(nfqnl_msg_config_cmds cmd){
|
||||||
struct nlmsghdr *nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num);
|
struct nlmsghdr *nlh = nfq_nlmsg_put(queue_msg_buffer, NFQNL_MSG_CONFIG, queue_num);
|
||||||
nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, cmd);
|
nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, cmd);
|
||||||
return mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
|
return mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ssize_t _recv_packet(char* buf, size_t buf_size = NFQUEUE_BUFFER_SIZE){
|
inline ssize_t _recv_packet(){
|
||||||
return mnl_socket_recvfrom(nl, buf, buf_size);
|
return mnl_socket_recvfrom(nl, queue_msg_buffer, NFQUEUE_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ using namespace std;
|
|||||||
using namespace Firegex::Regex;
|
using namespace Firegex::Regex;
|
||||||
using Firegex::NfQueue::MultiThreadQueue;
|
using Firegex::NfQueue::MultiThreadQueue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Compile options:
|
||||||
|
NFQUEUE_FAIL_OPEN - enable fail-open option of nfqueueß
|
||||||
|
---
|
||||||
|
USE_PIPES_FOR_BLOKING_QUEUE - use pipes instead of conditional variable, queue and mutex for blocking queue
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
void config_updater (){
|
void config_updater (){
|
||||||
string line;
|
string line;
|
||||||
while (true){
|
while (true){
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <tins/tcp_ip/stream_identifier.h>
|
#include <tins/tcp_ip/stream_identifier.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|||||||
@@ -91,12 +91,12 @@ public:
|
|||||||
stream_match = stream_search->second;
|
stream_match = stream_search->second;
|
||||||
}
|
}
|
||||||
err = hs_scan_stream(
|
err = hs_scan_stream(
|
||||||
stream_match,pkt->data.c_str(), pkt->data.size(),
|
stream_match,pkt->data, pkt->data_size,
|
||||||
0, scratch_space, match_func, &match_res
|
0, scratch_space, match_func, &match_res
|
||||||
);
|
);
|
||||||
}else{
|
}else{
|
||||||
err = hs_scan(
|
err = hs_scan(
|
||||||
regex_matcher,pkt->data.c_str(), pkt->data.size(),
|
regex_matcher,pkt->data, pkt->data_size,
|
||||||
0, scratch_space, match_func, &match_res
|
0, scratch_space, match_func, &match_res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handle_next_packet(NfQueue::PktRequest<RegexNfQueue>* pkt) override{
|
void handle_next_packet(NfQueue::PktRequest<RegexNfQueue>* pkt) override{
|
||||||
bool empty_payload = pkt->data.size() == 0;
|
bool empty_payload = pkt->data_size == 0;
|
||||||
if (pkt->tcp){
|
if (pkt->tcp){
|
||||||
match_ctx.matching_has_been_called = false;
|
match_ctx.matching_has_been_called = false;
|
||||||
match_ctx.pkt = pkt;
|
match_ctx.pkt = pkt;
|
||||||
|
|||||||
@@ -22,6 +22,36 @@ bool unhexlify(std::string const &hex, std::string &newString) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_PIPES_FOR_BLOKING_QUEUE
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class BlockingQueue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int pipefd[2];
|
||||||
|
public:
|
||||||
|
BlockingQueue(){
|
||||||
|
if (pipe(pipefd) == -1) {
|
||||||
|
throw std::runtime_error("pipe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(T new_value)
|
||||||
|
{
|
||||||
|
if (write(pipefd[1], &new_value, sizeof(T)) == -1) {
|
||||||
|
throw std::runtime_error("write");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void take(T& value)
|
||||||
|
{
|
||||||
|
if (read(pipefd[0], &value, sizeof(T)) == -1) {
|
||||||
|
throw std::runtime_error("read");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
template<typename T, int MAX = 1024> //same of kernel nfqueue max
|
template<typename T, int MAX = 1024> //same of kernel nfqueue max
|
||||||
class BlockingQueue
|
class BlockingQueue
|
||||||
{
|
{
|
||||||
@@ -32,6 +62,7 @@ private:
|
|||||||
std::condition_variable condNotFull;
|
std::condition_variable condNotFull;
|
||||||
size_t count; // Guard with Mutex
|
size_t count; // Guard with Mutex
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void put(T new_value)
|
void put(T new_value)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -61,4 +92,6 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // UTILS_CPP
|
#endif // UTILS_CPP
|
||||||
Reference in New Issue
Block a user