push: code changes x2

This commit is contained in:
Domingo Dirutigliano
2025-02-28 21:14:09 +01:00
parent 6a11dd0d16
commit 8ae533e8f7
31 changed files with 544 additions and 397 deletions

View File

@@ -16,7 +16,7 @@ RUN bun run build
FROM --platform=$TARGETARCH registry.fedoraproject.org/fedora:latest FROM --platform=$TARGETARCH registry.fedoraproject.org/fedora:latest
RUN dnf -y update && dnf install -y python3.13-devel @development-tools gcc-c++ \ RUN dnf -y update && dnf install -y python3.13-devel @development-tools gcc-c++ \
libnetfilter_queue-devel libnfnetlink-devel libmnl-devel libcap-ng-utils nftables \ libnetfilter_queue-devel libnfnetlink-devel libmnl-devel libcap-ng-utils nftables \
vectorscan-devel libtins-devel python3-nftables libpcap-devel boost-devel uv redis vectorscan-devel libtins-devel python3-nftables libpcap-devel boost-devel uv
RUN mkdir -p /execute/modules RUN mkdir -p /execute/modules
WORKDIR /execute WORKDIR /execute

View File

@@ -219,7 +219,7 @@ if __name__ == '__main__':
os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir(os.path.dirname(os.path.realpath(__file__)))
uvicorn.run( uvicorn.run(
"app:app", "app:app",
host="::" if DEBUG else None, host="0.0.0.0" if DEBUG else None,
port=FIREGEX_PORT, port=FIREGEX_PORT,
reload=DEBUG and not NORELOAD, reload=DEBUG and not NORELOAD,
access_log=True, access_log=True,

View File

@@ -7,6 +7,7 @@
#include <tins/tcp_ip/stream_identifier.h> #include <tins/tcp_ip/stream_identifier.h>
#include <libmnl/libmnl.h> #include <libmnl/libmnl.h>
#include <tins/tins.h> #include <tins/tins.h>
#include <map>
using namespace std; using namespace std;
@@ -17,7 +18,17 @@ enum class FilterAction{ DROP, ACCEPT, MANGLE, NOACTION };
enum class L4Proto { TCP, UDP, RAW }; enum class L4Proto { TCP, UDP, RAW };
typedef Tins::TCPIP::StreamIdentifier stream_id; typedef Tins::TCPIP::StreamIdentifier stream_id;
//TODO DUBBIO: I PACCHETTI INVIATI A PYTHON SONO GIA' FIXATI? struct tcp_ack_seq_ctx{
int64_t in = 0;
int64_t out = 0;
tcp_ack_seq_ctx(){}
void reset(){
in = 0;
out = 0;
}
};
typedef map<stream_id, tcp_ack_seq_ctx*> tcp_ack_map;
template<typename T> template<typename T>
class PktRequest { class PktRequest {
@@ -28,6 +39,7 @@ class PktRequest {
uint32_t packet_id; uint32_t packet_id;
size_t _original_size; size_t _original_size;
size_t _data_original_size; size_t _data_original_size;
size_t _header_size;
bool need_tcp_fixing = false; bool need_tcp_fixing = false;
public: public:
bool is_ipv6; bool is_ipv6;
@@ -39,18 +51,15 @@ class PktRequest {
bool is_input; bool is_input;
string packet; string packet;
char* data;
size_t data_size;
stream_id sid; stream_id sid;
int64_t* tcp_in_offset = nullptr; tcp_ack_seq_ctx* ack_seq_offset = nullptr;
int64_t* tcp_out_offset = nullptr;
T* ctx; T* ctx = nullptr;
private: private:
static size_t inner_data_size(Tins::PDU* pdu){ static inline size_t inner_data_size(Tins::PDU* pdu){
if (pdu == nullptr){ if (pdu == nullptr){
return 0; return 0;
} }
@@ -61,9 +70,9 @@ class PktRequest {
return inner->size(); return inner->size();
} }
inline void fetch_data_size(Tins::PDU* pdu){ inline void __internal_fetch_data_size(Tins::PDU* pdu){
data_size = inner_data_size(pdu); _data_original_size = inner_data_size(pdu);
_data_original_size = data_size; _header_size = _original_size - _data_original_size;
} }
L4Proto fill_l4_info(){ L4Proto fill_l4_info(){
@@ -72,14 +81,14 @@ class PktRequest {
if (tcp == nullptr){ if (tcp == nullptr){
udp = ipv6->find_pdu<Tins::UDP>(); udp = ipv6->find_pdu<Tins::UDP>();
if (udp == nullptr){ if (udp == nullptr){
fetch_data_size(ipv6); __internal_fetch_data_size(ipv6);
return L4Proto::RAW; return L4Proto::RAW;
}else{ }else{
fetch_data_size(udp); __internal_fetch_data_size(udp);
return L4Proto::UDP; return L4Proto::UDP;
} }
}else{ }else{
fetch_data_size(tcp); __internal_fetch_data_size(tcp);
return L4Proto::TCP; return L4Proto::TCP;
} }
}else{ }else{
@@ -87,73 +96,23 @@ class PktRequest {
if (tcp == nullptr){ if (tcp == nullptr){
udp = ipv4->find_pdu<Tins::UDP>(); udp = ipv4->find_pdu<Tins::UDP>();
if (udp == nullptr){ if (udp == nullptr){
fetch_data_size(ipv4); __internal_fetch_data_size(ipv4);
return L4Proto::RAW; return L4Proto::RAW;
}else{ }else{
fetch_data_size(udp); __internal_fetch_data_size(udp);
return L4Proto::UDP; return L4Proto::UDP;
} }
}else{ }else{
fetch_data_size(tcp); __internal_fetch_data_size(tcp);
return L4Proto::TCP; return L4Proto::TCP;
} }
} }
} }
bool need_tcp_fix(){ bool need_tcp_fix(){
return (tcp_in_offset != nullptr && *tcp_in_offset != 0) || (tcp_out_offset != nullptr && *tcp_out_offset != 0); return tcp && ack_seq_offset != nullptr && (ack_seq_offset->in != 0 || ack_seq_offset->out != 0);
} }
Tins::PDU::serialization_type reserialize_raw_data(const uint8_t* data, const size_t& data_size){
if (is_ipv6){
Tins::IPv6 ipv6_new = Tins::IPv6(data, data_size);
if (tcp){
Tins::TCP* tcp_new = ipv6_new.find_pdu<Tins::TCP>();
}
return ipv6_new.serialize();
}else{
Tins::IP ipv4_new = Tins::IP(data, data_size);
if (tcp){
Tins::TCP* tcp_new = ipv4_new.find_pdu<Tins::TCP>();
}
return ipv4_new.serialize();
}
}
void _fix_ack_seq_tcp(Tins::TCP* this_tcp){
need_tcp_fixing = need_tcp_fix();
#ifdef DEBUG
if (need_tcp_fixing){
cerr << "[DEBUG] Fixing ack_seq with offsets " << *tcp_in_offset << " " << *tcp_out_offset << endl;
}
#endif
if(this_tcp == nullptr){
return;
}
if (is_input){
if (tcp_in_offset != nullptr){
this_tcp->seq(this_tcp->seq() + *tcp_in_offset);
}
if (tcp_out_offset != nullptr){
this_tcp->ack_seq(this_tcp->ack_seq() - *tcp_out_offset);
}
}else{
if (tcp_in_offset != nullptr){
this_tcp->ack_seq(this_tcp->ack_seq() - *tcp_in_offset);
}
if (tcp_out_offset != nullptr){
this_tcp->seq(this_tcp->seq() + *tcp_out_offset);
}
}
#ifdef DEBUG
if (need_tcp_fixing){
size_t new_size = inner_data_size(this_tcp);
cerr << "[DEBUG] FIXED PKT " << (is_input?"-> IN ":"<- OUT") << " [SEQ: " << this_tcp->seq() << "] \t[ACK: " << this_tcp->ack_seq() << "] \t[SIZE: " << new_size << "]" << endl;
}
#endif
}
public: public:
PktRequest(const char* payload, size_t plen, 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):
@@ -168,22 +127,129 @@ class PktRequest {
sid = stream_id::make_identifier(*ipv6); sid = stream_id::make_identifier(*ipv6);
_original_size = ipv6->size(); _original_size = ipv6->size();
}else{ }else{
ipv4 = new Tins::IP((uint8_t*)packet.data(), plen); ipv4 = new Tins::IP((uint8_t*)packet.c_str(), plen);
sid = stream_id::make_identifier(*ipv4); sid = stream_id::make_identifier(*ipv4);
_original_size = ipv4->size(); _original_size = ipv4->size();
} }
l4_proto = fill_l4_info(); l4_proto = fill_l4_info();
data = packet.data()+(plen-data_size);
#ifdef DEBUG #ifdef DEBUG
if (tcp){ if (tcp){
cerr << "[DEBUG] NEW_PACKET " << (is_input?"-> IN ":"<- OUT") << " [SEQ: " << tcp->seq() << "] \t[ACK: " << tcp->ack_seq() << "] \t[SIZE: " << data_size << "]" << endl; cerr << "[DEBUG] NEW_PACKET " << (is_input?"-> IN ":"<- OUT") << " [SEQ: " << tcp->seq() << "] \t[ACK: " << tcp->ack_seq() << "] \t[SIZE: " << data_size() << "]" << endl;
} }
#endif #endif
} }
void fix_tcp_ack(){ inline size_t header_size(){
return _header_size;
}
char* data(){
return packet.data()+_header_size;
}
size_t data_size(){
return packet.size()-_header_size;
}
size_t data_original_size(){
return _data_original_size;
}
void reserialize(){
auto data = serialize();
packet.resize(data.size());
memcpy(packet.data(), data.data(), data.size());
}
void set_data(const char* data, const size_t& data_size){
auto bef_raw = before_raw_pdu_ptr();
if (bef_raw){
delete before_raw_pdu_ptr()->release_inner_pdu();
if (data_size > 0){
before_raw_pdu_ptr() /= move(Tins::RawPDU((uint8_t*)data, data_size));
}
}
}
Tins::PDU* before_raw_pdu_ptr(){
if (tcp){ if (tcp){
_fix_ack_seq_tcp(tcp); return tcp;
}else if (udp){
return udp;
}else if (ipv4){
return ipv4;
}else if (ipv6){
return ipv6;
}
return nullptr;
}
void set_packet(const char* data, size_t data_size){
// Parsing only the header with libtins
Tins::PDU *data_pdu = nullptr;
size_t total_size;
if (is_ipv6){
delete ipv6;
ipv6 = new Tins::IPv6((uint8_t*)data, data_size);
if (tcp){
tcp = ipv6->find_pdu<Tins::TCP>();
data_pdu = tcp;
}else if (udp){
udp = ipv6->find_pdu<Tins::UDP>();
data_pdu = udp;
}else{
data_pdu = ipv6;
}
total_size = ipv6->size();
}else{
delete ipv4;
ipv4 = new Tins::IP((uint8_t*)data, data_size);
if (tcp){
tcp = ipv4->find_pdu<Tins::TCP>();
data_pdu = tcp;
}else if(udp){
udp = ipv4->find_pdu<Tins::UDP>();
data_pdu = udp;
}else{
data_pdu = ipv4;
}
total_size = ipv4->size();
}
_header_size = total_size - inner_data_size(data_pdu);
// Libtins can skip data if the lenght is changed to a bigger len (due to ip header total lenght), so we need to specify the data section manually
set_data(data+_header_size, data_size-_header_size);
}
void fix_tcp_ack(){
need_tcp_fixing = need_tcp_fix();
if(!need_tcp_fixing){
return;
}
#ifdef DEBUG
cerr << "[DEBUG] Fixing ack_seq with offsets " << ((int32_t)ack_seq_offset->in) << " " << ((int32_t)ack_seq_offset->out) << endl;
#endif
if (is_input){
tcp->seq(tcp->seq() + ack_seq_offset->in);
tcp->ack_seq(tcp->ack_seq() - ack_seq_offset->out);
}else{
tcp->ack_seq(tcp->ack_seq() - ack_seq_offset->in);
tcp->seq(tcp->seq() + ack_seq_offset->out);
}
#ifdef DEBUG
size_t new_size = inner_data_size(tcp);
cerr << "[DEBUG] FIXED PKT " << (is_input?"-> IN ":"<- OUT") << " [SEQ: " << tcp->seq() << "] \t[ACK: " << tcp->ack_seq() << "] \t[SIZE: " << new_size << "]" << endl;
#endif
}
void fix_data_payload(){
//Stream follower move the payload data, so we need to reinizialize RawPDU
auto bef_raw = before_raw_pdu_ptr();
if (bef_raw){
delete bef_raw->release_inner_pdu();
auto new_data_size = packet.size()-_header_size;
if (new_data_size > 0){
bef_raw /= move(Tins::RawPDU((uint8_t*)packet.data()+_header_size, new_data_size));
}
} }
} }
@@ -196,10 +262,6 @@ class PktRequest {
} }
} }
size_t data_original_size(){
return _data_original_size;
}
size_t original_size(){ size_t original_size(){
return _original_size; return _original_size;
} }
@@ -225,11 +287,11 @@ class PktRequest {
void reject(){ void reject(){
if (tcp){ if (tcp){
//If the packet has data, we have to remove it //If the packet has data, we have to remove it
delete tcp->release_inner_pdu(); set_data(nullptr, 0);
//For the first matched data or only for data packets, we set FIN bit //For the first matched data or only for data packets, we set FIN bit
//This only for client packets, because this will trigger server to close the connection //This only for client packets, because this will trigger server to close the connection
//Packets will be filtered anyway also if client don't send packets //Packets will be filtered anyway also if client don't send packets
if (_data_original_size != 0 && is_input){ if (_data_original_size != 0){
tcp->set_flag(Tins::TCP::FIN,1); tcp->set_flag(Tins::TCP::FIN,1);
tcp->set_flag(Tins::TCP::ACK,1); tcp->set_flag(Tins::TCP::ACK,1);
tcp->set_flag(Tins::TCP::SYN,0); tcp->set_flag(Tins::TCP::SYN,0);
@@ -241,10 +303,16 @@ class PktRequest {
} }
} }
void mangle_custom_pkt(uint8_t* pkt, const size_t& pkt_size){ void mangle_custom_pkt(const char* raw_pkt, size_t raw_pkt_size){
if (action == FilterAction::NOACTION){ if (action == FilterAction::NOACTION){
action = FilterAction::MANGLE; try{
perfrom_action(pkt, pkt_size); set_packet(raw_pkt, raw_pkt_size);
reserialize();
action = FilterAction::MANGLE;
}catch(...){
action = FilterAction::DROP;
}
perfrom_action(false);
}else{ }else{
throw invalid_argument("Cannot mangle a packet that has already been accepted or dropped"); throw invalid_argument("Cannot mangle a packet that has already been accepted or dropped");
} }
@@ -259,7 +327,7 @@ class PktRequest {
delete ipv6; delete ipv6;
} }
inline Tins::PDU::serialization_type serialize(){ Tins::PDU::serialization_type serialize(){
if (is_ipv6){ if (is_ipv6){
return ipv6->serialize(); return ipv6->serialize();
}else{ }else{
@@ -268,15 +336,17 @@ class PktRequest {
} }
private: private:
void perfrom_action(uint8_t* custom_data = nullptr, size_t custom_data_size = 0){ void perfrom_action(bool do_serialize = true){
char buf[MNL_SOCKET_BUFFER_SIZE]; char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(res_id)); struct nlmsghdr *nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(res_id));
switch (action) switch (action)
{ {
case FilterAction::ACCEPT: case FilterAction::ACCEPT:
if (need_tcp_fixing){ if (need_tcp_fixing){
Tins::PDU::serialization_type data = serialize(); if (do_serialize){
nfq_nlmsg_verdict_put_pkt(nlh_verdict, data.data(), data.size()); reserialize();
}
nfq_nlmsg_verdict_put_pkt(nlh_verdict, packet.data(), packet.size());
} }
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT ); nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT );
break; break;
@@ -285,32 +355,20 @@ class PktRequest {
break; break;
case FilterAction::MANGLE:{ case FilterAction::MANGLE:{
//If not custom data, use the data in the packets //If not custom data, use the data in the packets
Tins::PDU::serialization_type data; if(do_serialize){
if (custom_data == nullptr){ reserialize();
data = serialize();
}else{
try{
data = reserialize_raw_data(custom_data, custom_data_size);
}catch(...){
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_DROP );
action = FilterAction::DROP;
break;
}
} }
nfq_nlmsg_verdict_put_pkt(nlh_verdict, packet.data(), packet.size());
#ifdef DEBUG #ifdef DEBUG
size_t new_size = _data_original_size+((int64_t)custom_data_size) - ((int64_t)_original_size); cerr << "[DEBUG] MANGLEDPKT " << (is_input?"-> IN ":"<- OUT") << " [SIZE: " << packet.size()-header_size() << "]" << endl;
cerr << "[DEBUG] MANGLEDPKT " << (is_input?"-> IN ":"<- OUT") << " [SIZE: " << new_size << "]" << endl;
#endif #endif
if (tcp && custom_data_size != _original_size){ if (tcp && ack_seq_offset && packet.size() != _original_size){
int64_t delta = ((int64_t)custom_data_size) - ((int64_t)_original_size); if (is_input){
ack_seq_offset->in += packet.size() - _original_size;
if (is_input && tcp_in_offset != nullptr){ }else{
*tcp_in_offset += delta; ack_seq_offset->out += packet.size() - _original_size;
}else if (!is_input && tcp_out_offset != nullptr){
*tcp_out_offset += delta;
} }
} }
nfq_nlmsg_verdict_put_pkt(nlh_verdict, data.data(), data.size());
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT ); nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT );
break; break;
} }

View File

@@ -16,6 +16,7 @@
#include <syncstream> #include <syncstream>
#include <iostream> #include <iostream>
#include "../classes/netfilter.cpp" #include "../classes/netfilter.cpp"
#include "../classes/nfqueue.cpp"
#include "stream_ctx.cpp" #include "stream_ctx.cpp"
#include "settings.cpp" #include "settings.cpp"
#include <Python.h> #include <Python.h>
@@ -46,7 +47,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue<PyProxyQueue> {
}; };
PyThreadState *tstate = NULL; PyThreadState *tstate = NULL;
NfQueue::PktRequest<PyProxyQueue>* pkt; NfQueue::PktRequest<PyProxyQueue>* pkt;
tcp_ack_seq_ctx* current_tcp_ack = nullptr; NfQueue::tcp_ack_seq_ctx* current_tcp_ack = nullptr;
void before_loop() override { void before_loop() override {
PyStatus pystatus; PyStatus pystatus;
@@ -89,7 +90,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue<PyProxyQueue> {
pyq->pkt->drop();// This is needed because the callback has to take the updated pkt pointer! pyq->pkt->drop();// This is needed because the callback has to take the updated pkt pointer!
} }
void filter_action(NfQueue::PktRequest<PyProxyQueue>* pkt, Stream& stream){ void filter_action(NfQueue::PktRequest<PyProxyQueue>* pkt, Stream& stream, const string& data){
auto stream_search = sctx.streams_ctx.find(pkt->sid); auto stream_search = sctx.streams_ctx.find(pkt->sid);
pyfilter_ctx* stream_match; pyfilter_ctx* stream_match;
if (stream_search == sctx.streams_ctx.end()){ if (stream_search == sctx.streams_ctx.end()){
@@ -108,7 +109,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue<PyProxyQueue> {
stream_match = stream_search->second; stream_match = stream_search->second;
} }
auto result = stream_match->handle_packet(pkt); auto result = stream_match->handle_packet(pkt, data);
switch(result.action){ switch(result.action){
case PyFilterResponse::ACCEPT: case PyFilterResponse::ACCEPT:
return pkt->accept(); return pkt->accept();
@@ -125,7 +126,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue<PyProxyQueue> {
stream.server_data_callback(bind(keep_fin_packet, this)); stream.server_data_callback(bind(keep_fin_packet, this));
return pkt->reject(); return pkt->reject();
case PyFilterResponse::MANGLE: case PyFilterResponse::MANGLE:
pkt->mangle_custom_pkt((uint8_t*)result.mangled_packet->data(), result.mangled_packet->size()); pkt->mangle_custom_pkt(result.mangled_packet->c_str(), result.mangled_packet->size());
if (pkt->get_action() == NfQueue::FilterAction::DROP){ if (pkt->get_action() == NfQueue::FilterAction::DROP){
cerr << "[error] [filter_action] Failed to mangle: the packet sent is not serializzable... the packet was dropped" << endl; cerr << "[error] [filter_action] Failed to mangle: the packet sent is not serializzable... the packet was dropped" << endl;
print_blocked_reason(*result.filter_match_by); print_blocked_reason(*result.filter_match_by);
@@ -146,20 +147,21 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue<PyProxyQueue> {
} }
static void on_data_recv(Stream& stream, PyProxyQueue* proxy_info, string data) { static void on_data_recv(Stream& stream, PyProxyQueue* pyq, const string& data) {
proxy_info->pkt->data = data.data(); pyq->pkt->fix_data_payload();
proxy_info->pkt->data_size = data.size(); pyq->filter_action(pyq->pkt, stream, data); //Only here the rebuilt_tcp_data is set
proxy_info->filter_action(proxy_info->pkt, stream);
} }
//Input data filtering //Input data filtering
static void on_client_data(Stream& stream, PyProxyQueue* proxy_info) { static void on_client_data(Stream& stream, PyProxyQueue* pyq) {
on_data_recv(stream, proxy_info, string(stream.client_payload().begin(), stream.client_payload().end())); auto data = stream.client_payload();
on_data_recv(stream, pyq, string((char*)data.data(), data.size()));
} }
//Server data filtering //Server data filtering
static void on_server_data(Stream& stream, PyProxyQueue* proxy_info) { static void on_server_data(Stream& stream, PyProxyQueue* pyq) {
on_data_recv(stream, proxy_info, string(stream.server_payload().begin(), stream.server_payload().end())); auto data = stream.server_payload();
on_data_recv(stream, pyq, string((char*)data.data(), data.size()));
} }
// A stream was terminated. The second argument is the reason why it was terminated // A stream was terminated. The second argument is the reason why it was terminated
@@ -178,10 +180,9 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue<PyProxyQueue> {
if (pyq->current_tcp_ack != nullptr){ if (pyq->current_tcp_ack != nullptr){
pyq->current_tcp_ack->reset(); pyq->current_tcp_ack->reset();
}else{ }else{
pyq->current_tcp_ack = new tcp_ack_seq_ctx(); pyq->current_tcp_ack = new NfQueue::tcp_ack_seq_ctx();
pyq->sctx.tcp_ack_ctx.insert_or_assign(pyq->pkt->sid, pyq->current_tcp_ack); pyq->sctx.tcp_ack_ctx.insert_or_assign(pyq->pkt->sid, pyq->current_tcp_ack);
pyq->pkt->tcp_in_offset = &pyq->current_tcp_ack->in_tcp_offset; pyq->pkt->ack_seq_offset = pyq->current_tcp_ack; // Set ack context
pyq->pkt->tcp_out_offset = &pyq->current_tcp_ack->out_tcp_offset;
} }
//Should not happen, but with this we can be sure about this //Should not happen, but with this we can be sure about this
@@ -205,18 +206,17 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue<PyProxyQueue> {
auto tcp_ack_search = sctx.tcp_ack_ctx.find(pkt->sid); auto tcp_ack_search = sctx.tcp_ack_ctx.find(pkt->sid);
if (tcp_ack_search != sctx.tcp_ack_ctx.end()){ if (tcp_ack_search != sctx.tcp_ack_ctx.end()){
current_tcp_ack = tcp_ack_search->second; current_tcp_ack = tcp_ack_search->second;
pkt->tcp_in_offset = &current_tcp_ack->in_tcp_offset; pkt->ack_seq_offset = current_tcp_ack;
pkt->tcp_out_offset = &current_tcp_ack->out_tcp_offset;
}else{ }else{
current_tcp_ack = nullptr; current_tcp_ack = nullptr;
//If necessary will be created by libtis new_stream callback //If necessary will be created by libtis new_stream callback
} }
pkt->fix_tcp_ack();
if (pkt->is_ipv6){ if (pkt->is_ipv6){
pkt->fix_tcp_ack();
follower.process_packet(*pkt->ipv6); follower.process_packet(*pkt->ipv6);
}else{ }else{
pkt->fix_tcp_ack();
follower.process_packet(*pkt->ipv4); follower.process_packet(*pkt->ipv4);
} }

View File

@@ -7,7 +7,9 @@
#include <map> #include <map>
#include <Python.h> #include <Python.h>
#include "../classes/netfilter.cpp" #include "../classes/netfilter.cpp"
#include "../classes/nfqueue.cpp"
#include "settings.cpp" #include "settings.cpp"
#include "../utils.cpp"
using namespace std; using namespace std;
@@ -50,17 +52,6 @@ struct py_filter_response {
typedef Tins::TCPIP::StreamIdentifier stream_id; typedef Tins::TCPIP::StreamIdentifier stream_id;
struct tcp_ack_seq_ctx{
//Can be negative, so we use int64_t (for a uint64_t value)
int64_t in_tcp_offset = 0;
int64_t out_tcp_offset = 0;
tcp_ack_seq_ctx(){}
void reset(){
in_tcp_offset = 0;
out_tcp_offset = 0;
}
};
struct pyfilter_ctx { struct pyfilter_ctx {
PyObject * glob = nullptr; PyObject * glob = nullptr;
@@ -105,12 +96,14 @@ struct pyfilter_ctx {
} }
py_filter_response handle_packet( py_filter_response handle_packet(
NfQueue::PktRequest<PyProxyQueue>* pkt NfQueue::PktRequest<PyProxyQueue>* pkt,
const string& data
){ ){
PyObject * packet_info = PyDict_New(); PyObject * packet_info = PyDict_New();
set_item_to_dict(packet_info, "data", PyBytes_FromStringAndSize(pkt->data, pkt->data_size)); pkt->reserialize();
set_item_to_dict(packet_info, "l4_size", PyLong_FromLong(pkt->data_original_size())); set_item_to_dict(packet_info, "data", PyBytes_FromStringAndSize(data.c_str(), data.size()));
set_item_to_dict(packet_info, "l4_size", PyLong_FromLong(pkt->data_size()));
set_item_to_dict(packet_info, "raw_packet", PyBytes_FromStringAndSize(pkt->packet.c_str(), pkt->packet.size())); set_item_to_dict(packet_info, "raw_packet", PyBytes_FromStringAndSize(pkt->packet.c_str(), pkt->packet.size()));
set_item_to_dict(packet_info, "is_input", PyBool_FromLong(pkt->is_input)); set_item_to_dict(packet_info, "is_input", PyBool_FromLong(pkt->is_input));
set_item_to_dict(packet_info, "is_ipv6", PyBool_FromLong(pkt->is_ipv6)); set_item_to_dict(packet_info, "is_ipv6", PyBool_FromLong(pkt->is_ipv6));
@@ -129,8 +122,7 @@ struct pyfilter_ctx {
#endif #endif
return py_filter_response(PyFilterResponse::EXCEPTION); return py_filter_response(PyFilterResponse::EXCEPTION);
} }
Py_DECREF(result); Py_DECREF(result);
result = get_item_from_glob("__firegex_pyfilter_result"); result = get_item_from_glob("__firegex_pyfilter_result");
@@ -235,11 +227,12 @@ struct pyfilter_ctx {
}; };
typedef map<stream_id, pyfilter_ctx*> matching_map; typedef map<stream_id, pyfilter_ctx*> matching_map;
typedef map<stream_id, tcp_ack_seq_ctx*> tcp_ack_map;
struct stream_ctx { struct stream_ctx {
matching_map streams_ctx; matching_map streams_ctx;
tcp_ack_map tcp_ack_ctx; NfQueue::tcp_ack_map tcp_ack_ctx;
void clean_stream_by_id(stream_id sid){ void clean_stream_by_id(stream_id sid){
auto stream_search = streams_ctx.find(sid); auto stream_search = streams_ctx.find(sid);

View File

@@ -20,6 +20,7 @@
#include "../classes/netfilter.cpp" #include "../classes/netfilter.cpp"
#include "stream_ctx.cpp" #include "stream_ctx.cpp"
#include "regex_rules.cpp" #include "regex_rules.cpp"
#include "../utils.cpp"
using namespace std; using namespace std;
@@ -30,8 +31,6 @@ namespace Regex {
using Tins::TCPIP::Stream; using Tins::TCPIP::Stream;
using Tins::TCPIP::StreamFollower; using Tins::TCPIP::StreamFollower;
class RegexNfQueue : public NfQueue::ThreadNfQueue<RegexNfQueue> { class RegexNfQueue : public NfQueue::ThreadNfQueue<RegexNfQueue> {
public: public:
stream_ctx sctx; stream_ctx sctx;
@@ -39,7 +38,7 @@ public:
StreamFollower follower; StreamFollower follower;
NfQueue::PktRequest<RegexNfQueue>* pkt; NfQueue::PktRequest<RegexNfQueue>* pkt;
bool filter_action(NfQueue::PktRequest<RegexNfQueue>* pkt){ bool filter_action(NfQueue::PktRequest<RegexNfQueue>* pkt, const string& data){
shared_ptr<RegexRules> conf = regex_config; shared_ptr<RegexRules> conf = regex_config;
auto current_version = conf->ver(); auto current_version = conf->ver();
@@ -85,12 +84,12 @@ public:
stream_match = stream_search->second; stream_match = stream_search->second;
} }
err = hs_scan_stream( err = hs_scan_stream(
stream_match,pkt->data, pkt->data_size, stream_match, data.c_str(), 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, pkt->data_size, regex_matcher, data.c_str(), data.size(),
0, scratch_space, match_func, &match_res 0, scratch_space, match_func, &match_res
); );
} }
@@ -102,7 +101,7 @@ public:
throw invalid_argument("Cannot close stream match on hyperscan"); throw invalid_argument("Cannot close stream match on hyperscan");
} }
if (err != HS_SUCCESS && err != HS_SCAN_TERMINATED) { if (err != HS_SUCCESS && err != HS_SCAN_TERMINATED) {
cerr << "[error] [filter_callback] Error while matching the stream (hs)" << endl; cerr << "[error] [filter_callback] Error while matching the stream (hs) " << err << endl;
throw invalid_argument("Error while matching the stream with hyperscan"); throw invalid_argument("Error while matching the stream with hyperscan");
} }
if (match_res.has_matched){ if (match_res.has_matched){
@@ -113,41 +112,13 @@ public:
return true; return true;
} }
void handle_next_packet(NfQueue::PktRequest<RegexNfQueue>* _pkt) override{
pkt = _pkt; // Setting packet context
if (pkt->tcp){
if (pkt->ipv4){
follower.process_packet(*pkt->ipv4);
}else{
follower.process_packet(*pkt->ipv6);
}
//Fallback to the default action
if (pkt->get_action() == NfQueue::FilterAction::NOACTION){
return pkt->accept();
}
}else{
if (!pkt->udp){
throw invalid_argument("Only TCP and UDP are supported");
}
if(pkt->data_size == 0){
return pkt->accept();
}else if (filter_action(pkt)){
return pkt->accept();
}else{
return pkt->drop();
}
}
}
//If the stream has already been matched, drop all data, and try to close the connection //If the stream has already been matched, drop all data, and try to close the connection
static void keep_fin_packet(RegexNfQueue* nfq){ static void keep_fin_packet(RegexNfQueue* nfq){
nfq->pkt->reject();// This is needed because the callback has to take the updated pkt pointer! nfq->pkt->reject(); // This is needed because the callback has to take the updated pkt pointer!
} }
static void on_data_recv(Stream& stream, RegexNfQueue* nfq, string data) { static void on_data_recv(Stream& stream, RegexNfQueue* nfq, const string& data) {
nfq->pkt->data = data.data(); if (!nfq->filter_action(nfq->pkt, data)){
nfq->pkt->data_size = data.size();
if (!nfq->filter_action(nfq->pkt)){
nfq->sctx.clean_stream_by_id(nfq->pkt->sid); nfq->sctx.clean_stream_by_id(nfq->pkt->sid);
stream.client_data_callback(bind(keep_fin_packet, nfq)); stream.client_data_callback(bind(keep_fin_packet, nfq));
stream.server_data_callback(bind(keep_fin_packet, nfq)); stream.server_data_callback(bind(keep_fin_packet, nfq));
@@ -157,12 +128,14 @@ public:
//Input data filtering //Input data filtering
static void on_client_data(Stream& stream, RegexNfQueue* nfq) { static void on_client_data(Stream& stream, RegexNfQueue* nfq) {
on_data_recv(stream, nfq, string(stream.client_payload().begin(), stream.client_payload().end())); auto data = stream.client_payload();
on_data_recv(stream, nfq, string((char*)data.data(), data.size()));
} }
//Server data filtering //Server data filtering
static void on_server_data(Stream& stream, RegexNfQueue* nfq) { static void on_server_data(Stream& stream, RegexNfQueue* nfq) {
on_data_recv(stream, nfq, string(stream.server_payload().begin(), stream.server_payload().end())); auto data = stream.server_payload();
on_data_recv(stream, nfq, string((char*)data.data(), data.size()));
} }
// A stream was terminated. The second argument is the reason why it was terminated // A stream was terminated. The second argument is the reason why it was terminated
@@ -181,6 +154,32 @@ public:
stream.stream_closed_callback(bind(on_stream_close, placeholders::_1, nfq)); stream.stream_closed_callback(bind(on_stream_close, placeholders::_1, nfq));
} }
void handle_next_packet(NfQueue::PktRequest<RegexNfQueue>* _pkt) override{
pkt = _pkt; // Setting packet context
if (pkt->tcp){
if (pkt->ipv4){
follower.process_packet(*pkt->ipv4);
}else{
follower.process_packet(*pkt->ipv6);
}
//Fallback to the default action
if (pkt->get_action() == NfQueue::FilterAction::NOACTION){
return pkt->accept();
}
}else{
if (!pkt->udp){
throw invalid_argument("Only TCP and UDP are supported");
}
if(pkt->data_size() == 0){
return pkt->accept();
}else if (filter_action(pkt, string(pkt->data(), pkt->data_size()))){
return pkt->accept();
}else{
return pkt->drop();
}
}
}
void before_loop() override{ void before_loop() override{
follower.new_stream_callback(bind(on_new_stream, placeholders::_1, this)); follower.new_stream_callback(bind(on_new_stream, placeholders::_1, this));
follower.stream_termination_callback(bind(on_stream_close, placeholders::_1, this)); follower.stream_termination_callback(bind(on_stream_close, placeholders::_1, this));

View File

@@ -5,9 +5,12 @@ import asyncio
import traceback import traceback
from fastapi import HTTPException from fastapi import HTTPException
import time import time
from utils import run_func
nft = FiregexTables() nft = FiregexTables()
OUTSTREAM_BUFFER_SIZE = 1024*10
class FiregexInterceptor: class FiregexInterceptor:
def __init__(self): def __init__(self):
@@ -28,14 +31,20 @@ class FiregexInterceptor:
self.sock_writer:asyncio.StreamWriter = None self.sock_writer:asyncio.StreamWriter = None
self.sock_conn_lock:asyncio.Lock self.sock_conn_lock:asyncio.Lock
self.last_time_exception = 0 self.last_time_exception = 0
self.outstrem_function = None
self.expection_function = None
self.outstrem_task: asyncio.Task
self.outstrem_buffer = ""
@classmethod @classmethod
async def start(cls, srv: Service): async def start(cls, srv: Service, outstream_func=None, exception_func=None):
self = cls() self = cls()
self.srv = srv self.srv = srv
self.filter_map_lock = asyncio.Lock() self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock() self.update_config_lock = asyncio.Lock()
self.sock_conn_lock = asyncio.Lock() self.sock_conn_lock = asyncio.Lock()
self.outstrem_function = outstream_func
self.expection_function = exception_func
if not self.sock_conn_lock.locked(): if not self.sock_conn_lock.locked():
await self.sock_conn_lock.acquire() await self.sock_conn_lock.acquire()
self.sock_path = f"/tmp/firegex_nfproxy_{srv.id}.sock" self.sock_path = f"/tmp/firegex_nfproxy_{srv.id}.sock"
@@ -50,16 +59,37 @@ class FiregexInterceptor:
await self.ack_lock.acquire() await self.ack_lock.acquire()
return self return self
async def _stream_handler(self):
while True:
try:
line = (await self.process.stdout.readuntil()).decode(errors="ignore")
print(line, end="")
except Exception as e:
self.ack_arrived = False
self.ack_status = False
self.ack_fail_what = "Can't read from nfq client"
self.ack_lock.release()
await self.stop()
raise HTTPException(status_code=500, detail="Can't read from nfq client") from e
self.outstrem_buffer+=line
if len(self.outstrem_buffer) > OUTSTREAM_BUFFER_SIZE:
self.outstrem_buffer = self.outstrem_buffer[-OUTSTREAM_BUFFER_SIZE:]+"\n"
if self.outstrem_function:
await run_func(self.outstrem_function, self.srv.id, line)
async def _start_binary(self): async def _start_binary(self):
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cpproxy") proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cpproxy")
self.process = await asyncio.create_subprocess_exec( self.process = await asyncio.create_subprocess_exec(
proxy_binary_path, stdin=asyncio.subprocess.DEVNULL, proxy_binary_path, stdin=asyncio.subprocess.DEVNULL,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
env={ env={
"NTHREADS": os.getenv("NTHREADS","1"), "NTHREADS": os.getenv("NTHREADS","1"),
"FIREGEX_NFQUEUE_FAIL_OPEN": "1" if self.srv.fail_open else "0", "FIREGEX_NFQUEUE_FAIL_OPEN": "1" if self.srv.fail_open else "0",
"FIREGEX_NFPROXY_SOCK": self.sock_path "FIREGEX_NFPROXY_SOCK": self.sock_path
}, },
) )
self.outstrem_task = asyncio.create_task(self._stream_handler())
try: try:
async with asyncio.timeout(3): async with asyncio.timeout(3):
await self.sock_conn_lock.acquire() await self.sock_conn_lock.acquire()
@@ -101,9 +131,7 @@ class FiregexInterceptor:
filter_name = line.split()[1] filter_name = line.split()[1]
print("BLOCKED", filter_name) print("BLOCKED", filter_name)
async with self.filter_map_lock: async with self.filter_map_lock:
print("LOCKED MAP LOCK")
if filter_name in self.filter_map: if filter_name in self.filter_map:
print("ADDING BLOCKED PACKET")
self.filter_map[filter_name].blocked_packets+=1 self.filter_map[filter_name].blocked_packets+=1
await self.filter_map[filter_name].update() await self.filter_map[filter_name].update()
if line.startswith("MANGLED "): if line.startswith("MANGLED "):
@@ -113,8 +141,9 @@ class FiregexInterceptor:
self.filter_map[filter_name].edited_packets+=1 self.filter_map[filter_name].edited_packets+=1
await self.filter_map[filter_name].update() await self.filter_map[filter_name].update()
if line.startswith("EXCEPTION"): if line.startswith("EXCEPTION"):
self.last_time_exception = time.time() self.last_time_exception = int(time.time()*1000) #ms timestamp
print("TODO EXCEPTION HANDLING") # TODO if self.expection_function:
await run_func(self.expection_function, self.srv.id, self.last_time_exception)
if line.startswith("ACK "): if line.startswith("ACK "):
self.ack_arrived = True self.ack_arrived = True
self.ack_status = line.split()[1].upper() == "OK" self.ack_status = line.split()[1].upper() == "OK"
@@ -132,6 +161,7 @@ class FiregexInterceptor:
self.server_task.cancel() self.server_task.cancel()
self.update_task.cancel() self.update_task.cancel()
self.unix_sock.close() self.unix_sock.close()
self.outstrem_task.cancel()
if os.path.exists(self.sock_path): if os.path.exists(self.sock_path):
os.remove(self.sock_path) os.remove(self.sock_path)
if self.process and self.process.returncode is None: if self.process and self.process.returncode is None:

View File

@@ -3,6 +3,7 @@ from modules.nfproxy.firegex import FiregexInterceptor
from modules.nfproxy.nftables import FiregexTables, FiregexFilter from modules.nfproxy.nftables import FiregexTables, FiregexFilter
from modules.nfproxy.models import Service, PyFilter from modules.nfproxy.models import Service, PyFilter
from utils.sqlite import SQLite from utils.sqlite import SQLite
from utils import run_func
class STATUS: class STATUS:
STOP = "stop" STOP = "stop"
@@ -11,13 +12,20 @@ class STATUS:
nft = FiregexTables() nft = FiregexTables()
class ServiceManager: class ServiceManager:
def __init__(self, srv: Service, db): def __init__(self, srv: Service, db, outstream_func=None, exception_func=None):
self.srv = srv self.srv = srv
self.db = db self.db = db
self.status = STATUS.STOP self.status = STATUS.STOP
self.filters: dict[str, FiregexFilter] = {} self.filters: dict[str, FiregexFilter] = {}
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
self.interceptor = None self.interceptor = None
self.outstream_function = outstream_func
self.last_exception_time = 0
async def excep_internal_handler(srv, exc_time):
self.last_exception_time = exc_time
if exception_func:
await run_func(exception_func, srv, exc_time)
self.exception_function = excep_internal_handler
async def _update_filters_from_db(self): async def _update_filters_from_db(self):
pyfilters = [ pyfilters = [
@@ -52,10 +60,16 @@ class ServiceManager:
self.status = status self.status = status
self.__update_status_db(status) self.__update_status_db(status)
def read_outstrem_buffer(self):
if self.interceptor:
return self.interceptor.outstrem_buffer
else:
return ""
async def start(self): async def start(self):
if not self.interceptor: if not self.interceptor:
nft.delete(self.srv) nft.delete(self.srv)
self.interceptor = await FiregexInterceptor.start(self.srv) self.interceptor = await FiregexInterceptor.start(self.srv, outstream_func=self.outstream_function, exception_func=self.exception_function)
await self._update_filters_from_db() await self._update_filters_from_db()
self._set_status(STATUS.ACTIVE) self._set_status(STATUS.ACTIVE)
@@ -75,10 +89,12 @@ class ServiceManager:
await self._update_filters_from_db() await self._update_filters_from_db()
class FirewallManager: class FirewallManager:
def __init__(self, db:SQLite): def __init__(self, db:SQLite, outstream_func=None, exception_func=None):
self.db = db self.db = db
self.service_table: dict[str, ServiceManager] = {} self.service_table: dict[str, ServiceManager] = {}
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
self.outstream_function = outstream_func
self.exception_function = exception_func
async def close(self): async def close(self):
for key in list(self.service_table.keys()): for key in list(self.service_table.keys()):
@@ -100,7 +116,7 @@ class FirewallManager:
srv = Service.from_dict(srv) srv = Service.from_dict(srv)
if srv.id in self.service_table: if srv.id in self.service_table:
continue continue
self.service_table[srv.id] = ServiceManager(srv, self.db) self.service_table[srv.id] = ServiceManager(srv, self.db, outstream_func=self.outstream_function, exception_func=self.exception_function)
await self.service_table[srv.id].next(srv.status) await self.service_table[srv.id].next(srv.status)
def get(self,srv_id) -> ServiceManager: def get(self,srv_id) -> ServiceManager:

View File

@@ -28,22 +28,22 @@ class FiregexTables(NFTableManager):
def __init__(self): def __init__(self):
super().__init__([ super().__init__([
{"add":{"chain":{ {"add":{"chain":{ #Input chain attached before conntrack see it
"family":"inet", "family":"inet",
"table":self.table_name, "table":self.table_name,
"name":self.input_chain, "name":self.input_chain,
"type":"filter", "type":"filter",
"hook":"prerouting", "hook":"prerouting",
"prio":-150, "prio":-301,
"policy":"accept" "policy":"accept"
}}}, }}},
{"add":{"chain":{ {"add":{"chain":{ #Output chain attached after conntrack saw it
"family":"inet", "family":"inet",
"table":self.table_name, "table":self.table_name,
"name":self.output_chain, "name":self.output_chain,
"type":"filter", "type":"filter",
"hook":"postrouting", "hook":"postrouting",
"prio":-150, "prio":-290,
"policy":"accept" "policy":"accept"
}}} }}}
],[ ],[

View File

@@ -26,7 +26,7 @@ class FiregexTables(NFTableManager):
"name":self.input_chain, "name":self.input_chain,
"type":"filter", "type":"filter",
"hook":"prerouting", "hook":"prerouting",
"prio":-150, "prio":-301,
"policy":"accept" "policy":"accept"
}}}, }}},
{"add":{"chain":{ {"add":{"chain":{
@@ -35,7 +35,7 @@ class FiregexTables(NFTableManager):
"name":self.output_chain, "name":self.output_chain,
"type":"filter", "type":"filter",
"hook":"postrouting", "hook":"postrouting",
"prio":-150, "prio":-301,
"policy":"accept" "policy":"accept"
}}} }}}
],[ ],[

View File

@@ -28,7 +28,7 @@ class FiregexTables(NFTableManager):
"name":self.prerouting_porthijack, "name":self.prerouting_porthijack,
"type":"filter", "type":"filter",
"hook":"prerouting", "hook":"prerouting",
"prio":-300, "prio":-310,
"policy":"accept" "policy":"accept"
}}}, }}},
{"add":{"chain":{ {"add":{"chain":{
@@ -37,7 +37,7 @@ class FiregexTables(NFTableManager):
"name":self.postrouting_porthijack, "name":self.postrouting_porthijack,
"type":"filter", "type":"filter",
"hook":"postrouting", "hook":"postrouting",
"prio":-300, "prio":-310,
"policy":"accept" "policy":"accept"
}}} }}}
],[ ],[

View File

@@ -14,6 +14,7 @@ from modules.nfproxy.nftables import convert_protocol_to_l4
import asyncio import asyncio
import traceback import traceback
from utils import DEBUG from utils import DEBUG
import utils
class ServiceModel(BaseModel): class ServiceModel(BaseModel):
service_id: str service_id: str
@@ -107,6 +108,10 @@ async def startup():
await firewall.init() await firewall.init()
except Exception as e: except Exception as e:
print("WARNING cannot start firewall:", e) print("WARNING cannot start firewall:", e)
utils.socketio.on("nfproxy-outstream-join", join_outstream)
utils.socketio.on("nfproxy-outstream-leave", leave_outstream)
utils.socketio.on("nfproxy-exception-join", join_exception)
utils.socketio.on("nfproxy-exception-leave", leave_exception)
async def shutdown(): async def shutdown():
db.backup() db.backup()
@@ -121,7 +126,13 @@ def gen_service_id():
break break
return res return res
firewall = FirewallManager(db) async def outstream_func(service_id, data):
await utils.socketio.emit(f"nfproxy-outstream-{service_id}", data, room=f"nfproxy-outstream-{service_id}")
async def exception_func(service_id, timestamp):
await utils.socketio.emit(f"nfproxy-exception-{service_id}", timestamp, room=f"nfproxy-exception-{service_id}")
firewall = FirewallManager(db, outstream_func=outstream_func, exception_func=exception_func)
@app.get('/services', response_model=list[ServiceModel]) @app.get('/services', response_model=list[ServiceModel])
async def get_service_list(): async def get_service_list():
@@ -355,3 +366,33 @@ async def get_pyfilters(service_id: str):
return f.read() return f.read()
except FileNotFoundError: except FileNotFoundError:
return "" return ""
#Socket io events
async def join_outstream(sid, data):
"""Client joins a room."""
srv = data.get("service")
if srv:
room = f"nfproxy-outstream-{srv}"
await utils.socketio.enter_room(sid, room)
await utils.socketio.emit(room, firewall.get(srv).read_outstrem_buffer(), room=sid)
async def leave_outstream(sid, data):
"""Client leaves a room."""
srv = data.get("service")
if srv:
await utils.socketio.leave_room(sid, f"nfproxy-outstream-{srv}")
async def join_exception(sid, data):
"""Client joins a room."""
srv = data.get("service")
if srv:
room = f"nfproxy-exception-{srv}"
await utils.socketio.enter_room(sid, room)
await utils.socketio.emit(room, firewall.get(srv).last_exception_time, room=sid)
async def leave_exception(sid, data):
"""Client leaves a room."""
srv = data.get("service")
if srv:
await utils.socketio.leave_room(sid, f"nfproxy-exception-{srv}")

View File

@@ -8,6 +8,7 @@ from fastapi.responses import FileResponse
from utils import DEBUG, ON_DOCKER, ROUTERS_DIR, list_files, run_func from utils import DEBUG, ON_DOCKER, ROUTERS_DIR, list_files, run_func
from utils.models import ResetRequest from utils.models import ResetRequest
import asyncio import asyncio
import traceback
REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/" REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/"
REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html") REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html")
@@ -70,6 +71,7 @@ def get_router_modules():
name=route name=route
)) ))
except Exception as e: except Exception as e:
traceback.print_exc()
print(f"Router {route} failed to load: {e}") print(f"Router {route} failed to load: {e}")
return res return res

View File

@@ -5,9 +5,8 @@ import { ImCross } from 'react-icons/im';
import { Outlet, Route, Routes } from 'react-router-dom'; import { Outlet, Route, Routes } from 'react-router-dom';
import MainLayout from './components/MainLayout'; import MainLayout from './components/MainLayout';
import { PasswordSend, ServerStatusResponse } from './js/models'; import { PasswordSend, ServerStatusResponse } from './js/models';
import { DEV_IP_BACKEND, errorNotify, getstatus, HomeRedirector, IS_DEV, login, setpassword } from './js/utils'; import { errorNotify, getstatus, HomeRedirector, IS_DEV, login, setpassword, socketio } from './js/utils';
import NFRegex from './pages/NFRegex'; import NFRegex from './pages/NFRegex';
import io from 'socket.io-client';
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails'; import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
import PortHijack from './pages/PortHijack'; import PortHijack from './pages/PortHijack';
import { Firewall } from './pages/Firewall'; import { Firewall } from './pages/Firewall';
@@ -15,22 +14,6 @@ import { useQueryClient } from '@tanstack/react-query';
import NFProxy from './pages/NFProxy'; import NFProxy from './pages/NFProxy';
import ServiceDetailsNFProxy from './pages/NFProxy/ServiceDetails'; import ServiceDetailsNFProxy from './pages/NFProxy/ServiceDetails';
export const socket = import.meta.env.DEV?
io("ws://"+DEV_IP_BACKEND, {
path:"/sock/socket.io",
transports: ['websocket'],
auth: {
token: localStorage.getItem("access_token")
}
}):
io({
path:"/sock/socket.io",
transports: ['websocket'],
auth: {
token: localStorage.getItem("access_token")
}
})
function App() { function App() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -162,16 +145,16 @@ const PageRouting = ({ getStatus }:{ getStatus:()=>void }) => {
useEffect(()=>{ useEffect(()=>{
getStatus() getStatus()
socket.on("update", (data) => { socketio.on("update", (data) => {
queryClient.invalidateQueries({ queryKey: data }) queryClient.invalidateQueries({ queryKey: data })
}) })
socket.on("connect_error", (err) => { socketio.on("connect_error", (err) => {
errorNotify("Socket.Io connection failed! ",`Error message: [${err.message}]`) errorNotify("Socket.Io connection failed! ",`Error message: [${err.message}]`)
getStatus() getStatus()
}); });
return () => { return () => {
socket.off("update") socketio.off("update")
socket.off("connect_error") socketio.off("connect_error")
} }
},[]) },[])

View File

@@ -31,8 +31,6 @@ function HeaderPage(props: any) {
const [changePasswordModal, setChangePasswordModal] = useState(false); const [changePasswordModal, setChangePasswordModal] = useState(false);
const [resetFiregexModal, setResetFiregexModal] = useState(false); const [resetFiregexModal, setResetFiregexModal] = useState(false);
const [tooltipHomeOpened, setTooltipHomeOpened] = useState(false);
const [tooltipLogoutOpened,setTooltipLogoutOpened] = useState(false);
return <AppShell.Header className="firegex__header__header" {...props}> return <AppShell.Header className="firegex__header__header" {...props}>
<Burger <Burger
hiddenFrom='md' hiddenFrom='md'
@@ -64,19 +62,16 @@ function HeaderPage(props: any) {
<Menu.Item color="red" leftSection={<MdOutlineSettingsBackupRestore size={18} />} onClick={() => setResetFiregexModal(true)}>Reset Firegex</Menu.Item> <Menu.Item color="red" leftSection={<MdOutlineSettingsBackupRestore size={18} />} onClick={() => setResetFiregexModal(true)}>Reset Firegex</Menu.Item>
</MenuDropDownWithButton> </MenuDropDownWithButton>
<Space w="md" /> <Space w="md" />
<Tooltip label="Home" position='bottom' color="teal" opened={tooltipHomeOpened}> <Tooltip label="Home" position='bottom' color="teal">
<ActionIcon color="teal" style={{marginRight:"10px"}} <ActionIcon color="teal" style={{marginRight:"10px"}}
size="xl" radius="md" variant="filled" size="xl" radius="md" variant="filled"
onClick={go_to_home} onClick={go_to_home}>
onFocus={() => setTooltipHomeOpened(false)} onBlur={() => setTooltipHomeOpened(false)}
onMouseEnter={() => setTooltipHomeOpened(true)} onMouseLeave={() => setTooltipHomeOpened(false)}>
<AiFillHome size="25px" /> <AiFillHome size="25px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip label="Logout" position='bottom' color="blue" opened={tooltipLogoutOpened}> <Tooltip label="Logout" position='bottom' color="blue">
<ActionIcon color="blue" onClick={logout_action} size="xl" radius="md" variant="filled" <ActionIcon color="blue" onClick={logout_action} size="xl" radius="md" variant="filled">
onFocus={() => setTooltipLogoutOpened(false)} onBlur={() => setTooltipLogoutOpened(false)} <ImExit size={23} style={{marginTop:"3px", marginLeft:"2px"}}/></ActionIcon>
onMouseEnter={() => setTooltipLogoutOpened(true)} onMouseLeave={() => setTooltipLogoutOpened(false)}><ImExit size={23} style={{marginTop:"3px", marginLeft:"2px"}}/></ActionIcon>
</Tooltip> </Tooltip>
<ResetPasswordModal opened={changePasswordModal} onClose={() => setChangePasswordModal(false)} /> <ResetPasswordModal opened={changePasswordModal} onClose={() => setChangePasswordModal(false)} />
<ResetModal opened={resetFiregexModal} onClose={() => setResetFiregexModal(false)} /> <ResetModal opened={resetFiregexModal} onClose={() => setResetFiregexModal(false)} />

View File

@@ -0,0 +1,17 @@
import { Code, Modal, ScrollArea } from "@mantine/core"
export const ModalLog = (
{ title, opened, close, data }:
{
title: string,
opened: boolean,
close: () => void,
data: string,
}
) => {
return <Modal size="90%" title={title} opened={opened} onClose={close} centered>
<ScrollArea h={500} style={{ minWidth: "100%",whiteSpace: "pre-wrap"}}>
<Code block mih={500}>{data}</Code>
</ScrollArea>
</Modal>
}

View File

@@ -0,0 +1,27 @@
import { IoIosWarning } from "react-icons/io"
import { socketio, WARNING_NFPROXY_TIME_LIMIT } from "../../js/utils"
import { Tooltip } from "@mantine/core"
import { useEffect, useState } from "react"
export const ExceptionWarning = ({ service_id }: { service_id: string }) => {
const [lastExceptionTimestamp, setLastExceptionTimestamp] = useState<number>(0)
useEffect(() => {
socketio.emit("nfproxy-exception-join", { service: service_id });
socketio.on(`nfproxy-exception-${service_id}`, (data) => {
setLastExceptionTimestamp(data)
});
return () => {
socketio.emit("nfproxy-exception-leave", { service: service_id });
}
}, [])
return <>
{(new Date().getTime()-lastExceptionTimestamp <= WARNING_NFPROXY_TIME_LIMIT)?
<Tooltip label={`There was an exception less than ${WARNING_NFPROXY_TIME_LIMIT/(1000*60)} minutes ago: check the logs`} color="yellow">
<IoIosWarning size={30} style={{ color: "yellow" }} />
</Tooltip>
:null}
</>
}

View File

@@ -15,6 +15,7 @@ import { FaFilter } from "react-icons/fa";
import { IoSettingsSharp } from 'react-icons/io5'; import { IoSettingsSharp } from 'react-icons/io5';
import AddEditService from '../AddEditService'; import AddEditService from '../AddEditService';
import { FaPencilAlt } from "react-icons/fa"; import { FaPencilAlt } from "react-icons/fa";
import { ExceptionWarning } from '../ExceptionWarning';
export default function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) { export default function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
@@ -26,7 +27,6 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [buttonLoading, setButtonLoading] = useState(false) const [buttonLoading, setButtonLoading] = useState(false)
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [deleteModal, setDeleteModal] = useState(false) const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false) const [renameModal, setRenameModal] = useState(false)
const [editModal, setEditModal] = useState(false) const [editModal, setEditModal] = useState(false)
@@ -109,6 +109,8 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
</Box> </Box>
{isMedium?<Space w="xl" />:<Space h="lg" />} {isMedium?<Space w="xl" />:<Space h="lg" />}
<Box className="center-flex"> <Box className="center-flex">
<ExceptionWarning service_id={service.service_id} />
<Space w="sm"/>
<MenuDropDownWithButton> <MenuDropDownWithButton>
<Menu.Item><b>Edit service</b></Menu.Item> <Menu.Item><b>Edit service</b></Menu.Item>
<Menu.Item leftSection={<IoSettingsSharp size={18} />} onClick={()=>setEditModal(true)}>Service Settings</Menu.Item> <Menu.Item leftSection={<IoSettingsSharp size={18} />} onClick={()=>setEditModal(true)}>Service Settings</Menu.Item>
@@ -118,13 +120,11 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
<Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item> <Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</MenuDropDownWithButton> </MenuDropDownWithButton>
<Space w="md"/> <Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}> <Tooltip label="Stop service" zIndex={0} color="red">
<ActionIcon color="red" loading={buttonLoading} <ActionIcon color="red" loading={buttonLoading}
onClick={stopService} size="xl" radius="md" variant="filled" onClick={stopService} size="xl" radius="md" variant="filled"
disabled={service.status === "stop"} disabled={service.status === "stop"}
aria-describedby="tooltip-stop-id" aria-describedby="tooltip-stop-id">
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
<FaStop size="20px" /> <FaStop size="20px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>

View File

@@ -25,7 +25,6 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [buttonLoading, setButtonLoading] = useState(false) const [buttonLoading, setButtonLoading] = useState(false)
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [deleteModal, setDeleteModal] = useState(false) const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false) const [renameModal, setRenameModal] = useState(false)
const [editModal, setEditModal] = useState(false) const [editModal, setEditModal] = useState(false)
@@ -115,13 +114,11 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli
<Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item> <Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</MenuDropDownWithButton> </MenuDropDownWithButton>
<Space w="md"/> <Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}> <Tooltip label="Stop service" zIndex={0} color="red">
<ActionIcon color="red" loading={buttonLoading} <ActionIcon color="red" loading={buttonLoading}
onClick={stopService} size="xl" radius="md" variant="filled" onClick={stopService} size="xl" radius="md" variant="filled"
disabled={service.status === "stop"} disabled={service.status === "stop"}
aria-describedby="tooltip-stop-id" aria-describedby="tooltip-stop-id">
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
<FaStop size="20px" /> <FaStop size="20px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>

View File

@@ -1,5 +1,5 @@
import { ActionIcon, Badge, Box, Divider, Menu, Space, Title, Tooltip } from '@mantine/core'; import { ActionIcon, Badge, Box, Divider, Menu, Space, Title, Tooltip } from '@mantine/core';
import React, { useState } from 'react'; import { useState } from 'react';
import { FaPlay, FaStop } from 'react-icons/fa'; import { FaPlay, FaStop } from 'react-icons/fa';
import { porthijack, Service } from '../utils'; import { porthijack, Service } from '../utils';
import YesNoModal from '../../YesNoModal'; import YesNoModal from '../../YesNoModal';
@@ -17,11 +17,9 @@ export default function ServiceRow({ service }:{ service:Service }) {
let status_color = service.active ? "teal": "red" let status_color = service.active ? "teal": "red"
const [buttonLoading, setButtonLoading] = useState(false) const [buttonLoading, setButtonLoading] = useState(false)
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [deleteModal, setDeleteModal] = useState(false) const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false) const [renameModal, setRenameModal] = useState(false)
const [changeDestModal, setChangeDestModal] = useState(false) const [changeDestModal, setChangeDestModal] = useState(false)
const portInputRef = React.createRef<HTMLInputElement>()
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
const form = useForm({ const form = useForm({
@@ -113,13 +111,11 @@ export default function ServiceRow({ service }:{ service:Service }) {
<Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item> <Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</MenuDropDownWithButton> </MenuDropDownWithButton>
<Space w="md"/> <Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}> <Tooltip label="Stop service" zIndex={0} color="red">
<ActionIcon color="red" loading={buttonLoading} <ActionIcon color="red" loading={buttonLoading}
onClick={stopService} size="xl" radius="md" variant="filled" onClick={stopService} size="xl" radius="md" variant="filled"
disabled={!service.active} disabled={!service.active}
aria-describedby="tooltip-stop-id" aria-describedby="tooltip-stop-id">
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
<FaStop size="20px" /> <FaStop size="20px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>

View File

@@ -9,7 +9,6 @@ import { FaPencilAlt } from 'react-icons/fa';
export default function PyFilterView({ filterInfo }:{ filterInfo:PyFilter }) { export default function PyFilterView({ filterInfo }:{ filterInfo:PyFilter }) {
const [statusTooltipOpened, setStatusTooltipOpened] = useState(false);
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
const changeRegexStatus = () => { const changeRegexStatus = () => {
@@ -24,24 +23,22 @@ export default function PyFilterView({ filterInfo }:{ filterInfo:PyFilter }) {
return <Box my="sm" display="flex" style={{alignItems:"center"}}> return <Box my="sm" display="flex" style={{alignItems:"center"}}>
<Text className="firegex__regexview__pyfilter_text" style={{ width: "100%", alignItems: "center"}} display="flex" > <Box className="firegex__regexview__pyfilter_text" style={{ width: "100%", alignItems: "center"}} display="flex" >
<Badge size="sm" radius="lg" mr="sm" color={filterInfo.active?"lime":"red"} variant="filled" /> <Badge size="sm" radius="lg" mr="sm" color={filterInfo.active?"lime":"red"} variant="filled" />
{filterInfo.name} {filterInfo.name}
<Box className='flex-spacer' /> <Box className='flex-spacer' />
<Space w="xs" />
{isMedium?<>
<Badge size="md" radius="md" color="yellow" variant="filled"><FaFilter style={{ marginBottom: -2, marginRight: 2}} /> {filterInfo.blocked_packets}</Badge>
<Space w="xs" /> <Space w="xs" />
<Badge size="md" radius="md" color="orange" variant="filled"><FaPencilAlt style={{ marginBottom: -1, marginRight: 2}} /> {filterInfo.edited_packets}</Badge> {isMedium?<>
<Space w="lg" /> <Badge size="md" radius="md" color="yellow" variant="filled"><FaFilter style={{ marginBottom: -2, marginRight: 2}} /> {filterInfo.blocked_packets}</Badge>
</>:null} <Space w="xs" />
<Tooltip label={filterInfo.active?"Deactivate":"Activate"} zIndex={0} color={filterInfo.active?"orange":"teal"} opened={statusTooltipOpened}> <Badge size="md" radius="md" color="orange" variant="filled"><FaPencilAlt style={{ marginBottom: -1, marginRight: 2}} /> {filterInfo.edited_packets}</Badge>
<ActionIcon color={filterInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="lg" radius="md" variant="filled" <Space w="lg" />
onFocus={() => setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)} </>:null}
onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)} <Tooltip label={filterInfo.active?"Deactivate":"Activate"} zIndex={0} color={filterInfo.active?"orange":"teal"}>
>{filterInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon> <ActionIcon color={filterInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="lg" radius="md" variant="filled">
</Tooltip> {filterInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon>
</Text> </Tooltip>
</Box>
</Box> </Box>
} }

View File

@@ -19,10 +19,7 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
let regex_expr = b64decode(regexInfo.regex); let regex_expr = b64decode(regexInfo.regex);
const [deleteModal, setDeleteModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false);
const [deleteTooltipOpened, setDeleteTooltipOpened] = useState(false);
const [statusTooltipOpened, setStatusTooltipOpened] = useState(false);
const clipboard = useClipboard({ timeout: 500 }); const clipboard = useClipboard({ timeout: 500 });
const isMedium = isMediumScreen();
const deleteRegex = () => { const deleteRegex = () => {
nfregex.regexdelete(regexInfo.id).then(res => { nfregex.regexdelete(regexInfo.id).then(res => {
@@ -54,18 +51,14 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
}}>{regex_expr}</Text> }}>{regex_expr}</Text>
</Box> </Box>
<Space w="xs" /> <Space w="xs" />
<Tooltip label={regexInfo.active?"Deactivate":"Activate"} zIndex={0} color={regexInfo.active?"orange":"teal"} opened={statusTooltipOpened}> <Tooltip label={regexInfo.active?"Deactivate":"Activate"} zIndex={0} color={regexInfo.active?"orange":"teal"}>
<ActionIcon color={regexInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="xl" radius="md" variant="filled" <ActionIcon color={regexInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="xl" radius="md" variant="filled"
onFocus={() => setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)}
onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)}
>{regexInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon> >{regexInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon>
</Tooltip> </Tooltip>
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Delete regex" zIndex={0} color="red" opened={deleteTooltipOpened} > <Tooltip label="Delete regex" zIndex={0} color="red" >
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled" <ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled">
onFocus={() => setDeleteTooltipOpened(false)} onBlur={() => setDeleteTooltipOpened(false)} <BsTrashFill size={22} /></ActionIcon>
onMouseEnter={() => setDeleteTooltipOpened(true)} onMouseLeave={() => setDeleteTooltipOpened(false)}
><BsTrashFill size={22} /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
<Box display="flex" mt="sm" ml="xs"> <Box display="flex" mt="sm" ml="xs">

View File

@@ -2,12 +2,11 @@ import { showNotification } from "@mantine/notifications";
import { ImCross } from "react-icons/im"; import { ImCross } from "react-icons/im";
import { TiTick } from "react-icons/ti" import { TiTick } from "react-icons/ti"
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { nfregex } from "../components/NFRegex/utils";
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models"; import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
import { Buffer } from "buffer" import { Buffer } from "buffer"
import { QueryClient, useQuery } from "@tanstack/react-query"; import { QueryClient, useQuery } from "@tanstack/react-query";
import { useMediaQuery } from "@mantine/hooks"; import { useMediaQuery } from "@mantine/hooks";
import { nfproxy } from "../components/NFProxy/utils"; import { io } from "socket.io-client";
export const IS_DEV = import.meta.env.DEV export const IS_DEV = import.meta.env.DEV
@@ -19,6 +18,24 @@ export const regex_port = "^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|
export const regex_range_port = "^(([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?)?)?$" export const regex_range_port = "^(([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?)?)?$"
export const DEV_IP_BACKEND = "127.0.0.1:4444" export const DEV_IP_BACKEND = "127.0.0.1:4444"
export const WARNING_NFPROXY_TIME_LIMIT = 1000*60*10 // 10 minutes
export const socketio = import.meta.env.DEV?
io("ws://"+DEV_IP_BACKEND, {
path:"/sock/socket.io",
transports: ['websocket'],
auth: {
token: localStorage.getItem("access_token")
}
}):
io({
path:"/sock/socket.io",
transports: ['websocket'],
auth: {
token: localStorage.getItem("access_token")
}
})
export const queryClient = new QueryClient({ defaultOptions: { queries: { export const queryClient = new QueryClient({ defaultOptions: { queries: {
staleTime: Infinity staleTime: Infinity
} }}) } }})

View File

@@ -25,12 +25,7 @@ import { PiWallLight } from "react-icons/pi";
export const Firewall = () => { export const Firewall = () => {
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
const [tooltipApplyOpened, setTooltipApplyOpened] = useState(false);
const [tooltipSettingsOpened, setTooltipSettingsOpened] = useState(false);
const [currentPolicy, setCurrentPolicy] = useState<ActionType>(ActionType.ACCEPT) const [currentPolicy, setCurrentPolicy] = useState<ActionType>(ActionType.ACCEPT)
const [tooltipAddRulOpened, setTooltipAddRulOpened] = useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const rules = firewallRulesQuery() const rules = firewallRulesQuery()
const [state, handlers] = useListState<Rule & {rule_id:string}>([]); const [state, handlers] = useListState<Rule & {rule_id:string}>([]);
@@ -364,31 +359,22 @@ export const Firewall = () => {
<Space w="xs" /> <Space w="xs" />
<Badge size="md" radius="sm" color="green" variant="filled"><FaDirections style={{ marginBottom: -1, marginRight: 4}}/>Rules: {rules.isLoading?0:rules.data?.rules.length}</Badge> <Badge size="md" radius="sm" color="green" variant="filled"><FaDirections style={{ marginBottom: -1, marginRight: 4}}/>Rules: {rules.isLoading?0:rules.data?.rules.length}</Badge>
<Space w="md" /> <Space w="md" />
<Tooltip label="Add a new rule" position='bottom' color="blue" opened={tooltipAddOpened}> <Tooltip label="Add a new rule" position='bottom' color="blue">
<ActionIcon color="blue" onClick={emptyRuleAdd} size="lg" radius="md" variant="filled" <ActionIcon color="blue" onClick={emptyRuleAdd} size="lg" radius="md" variant="filled"><BsPlusLg size={18} /></ActionIcon>
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
</Tooltip> </Tooltip>
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}> <Tooltip label="Refresh" position='bottom' color="indigo">
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["firewall"])} size="lg" radius="md" variant="filled" <ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["firewall"])} size="lg" radius="md" variant="filled"
loading={rules.isFetching} loading={rules.isFetching}><TbReload size={18} /></ActionIcon>
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
</Tooltip> </Tooltip>
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Settings" position='bottom' color="cyan" opened={tooltipSettingsOpened}> <Tooltip label="Settings" position='bottom' color="cyan">
<ActionIcon color="cyan" onClick={()=>setSettingsModal(true)} size="lg" radius="md" variant="filled" <ActionIcon color="cyan" onClick={()=>setSettingsModal(true)} size="lg" radius="md" variant="filled"><IoSettingsSharp size={18} /></ActionIcon>
onFocus={() => setTooltipSettingsOpened(false)} onBlur={() => setTooltipSettingsOpened(false)}
onMouseEnter={() => setTooltipSettingsOpened(true)} onMouseLeave={() => setTooltipSettingsOpened(false)}><IoSettingsSharp size={18} /></ActionIcon>
</Tooltip> </Tooltip>
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Apply" position='bottom' color="grape" opened={tooltipApplyOpened}> <Tooltip label="Apply" position='bottom' color="grape">
<ActionIcon color="grape" onClick={applyChanges} size="lg" radius="md" variant="filled" <ActionIcon color="grape" onClick={applyChanges} size="lg" radius="md" variant="filled" disabled={!valuesChanged}>
onFocus={() => setTooltipApplyOpened(false)} onBlur={() => setTooltipApplyOpened(false)} <TiTick size={22} /></ActionIcon>
onMouseEnter={() => setTooltipApplyOpened(true)} onMouseLeave={() => setTooltipApplyOpened(false)}
disabled={!valuesChanged}
><TiTick size={22} /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</Box> </Box>
@@ -424,10 +410,9 @@ export const Firewall = () => {
<Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No rule found! Add one clicking the "+" buttons</Title> <Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No rule found! Add one clicking the "+" buttons</Title>
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
<Box className='center-flex'> <Box className='center-flex'>
<Tooltip label="Add a new rule" color="blue" opened={tooltipAddRulOpened}> <Tooltip label="Add a new rule" color="blue">
<ActionIcon color="blue" onClick={emptyRuleAdd} size="xl" radius="md" variant="filled" <ActionIcon color="blue" onClick={emptyRuleAdd} size="xl" radius="md" variant="filled">
onFocus={() => setTooltipAddRulOpened(false)} onBlur={() => setTooltipAddRulOpened(false)} <BsPlusLg size="20px" /></ActionIcon>
onMouseEnter={() => setTooltipAddRulOpened(true)} onMouseLeave={() => setTooltipAddRulOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</>} </>}

View File

@@ -1,12 +1,12 @@
import { ActionIcon, Box, Code, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core'; import { ActionIcon, Box, Code, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import { Navigate, useNavigate, useParams } from 'react-router-dom'; import { Navigate, useNavigate, useParams } from 'react-router-dom';
import { Badge, Divider, Menu } from '@mantine/core'; import { Badge, Divider, Menu } from '@mantine/core';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { FaFilter, FaPencilAlt, FaPlay, FaStop } from 'react-icons/fa'; import { FaFilter, FaPencilAlt, FaPlay, FaStop } from 'react-icons/fa';
import { nfproxy, nfproxyServiceFilterCodeQuery, nfproxyServicePyfiltersQuery, nfproxyServiceQuery, serviceQueryKey } from '../../components/NFProxy/utils'; import { nfproxy, nfproxyServiceFilterCodeQuery, nfproxyServicePyfiltersQuery, nfproxyServiceQuery, serviceQueryKey } from '../../components/NFProxy/utils';
import { MdDoubleArrow } from "react-icons/md" import { MdDoubleArrow } from "react-icons/md"
import YesNoModal from '../../components/YesNoModal'; import YesNoModal from '../../components/YesNoModal';
import { errorNotify, isMediumScreen, okNotify, regex_ipv4 } from '../../js/utils'; import { errorNotify, isMediumScreen, okNotify, regex_ipv4, socketio } from '../../js/utils';
import { BsTrashFill } from 'react-icons/bs'; import { BsTrashFill } from 'react-icons/bs';
import { BiRename } from 'react-icons/bi' import { BiRename } from 'react-icons/bi'
import RenameForm from '../../components/NFProxy/ServiceRow/RenameForm'; import RenameForm from '../../components/NFProxy/ServiceRow/RenameForm';
@@ -19,6 +19,10 @@ import PyFilterView from '../../components/PyFilterView';
import { TbPlugConnected } from 'react-icons/tb'; import { TbPlugConnected } from 'react-icons/tb';
import { CodeHighlight } from '@mantine/code-highlight'; import { CodeHighlight } from '@mantine/code-highlight';
import { FaPython } from "react-icons/fa"; import { FaPython } from "react-icons/fa";
import { FiFileText } from "react-icons/fi";
import { ModalLog } from '../../components/ModalLog';
import { useListState } from '@mantine/hooks';
import { ExceptionWarning } from '../../components/NFProxy/ExceptionWarning';
export default function ServiceDetailsNFProxy() { export default function ServiceDetailsNFProxy() {
@@ -31,11 +35,30 @@ export default function ServiceDetailsNFProxy() {
const [editModal, setEditModal] = useState(false) const [editModal, setEditModal] = useState(false)
const [buttonLoading, setButtonLoading] = useState(false) const [buttonLoading, setButtonLoading] = useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [tooltipBackOpened, setTooltipBackOpened] = useState(false);
const filterCode = nfproxyServiceFilterCodeQuery(srv??"") const filterCode = nfproxyServiceFilterCodeQuery(srv??"")
const navigate = useNavigate() const navigate = useNavigate()
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
const [openLogModal, setOpenLogModal] = useState(false)
const [logData, logDataSetters] = useListState<string>([]);
useEffect(()=>{
if (srv){
if (openLogModal){
socketio.emit("nfproxy-outstream-join", { service: srv });
socketio.on(`nfproxy-outstream-${srv}`, (data) => {
logDataSetters.append(data)
});
}else{
logDataSetters.setState([])
socketio.emit("nfproxy-outstream-leave", { service: srv });
}
return () => {
logDataSetters.setState([])
socketio.emit("nfproxy-outstream-leave", { service: srv });
}
}
}, [openLogModal, srv])
if (services.isLoading) return <LoadingOverlay visible={true} /> if (services.isLoading) return <LoadingOverlay visible={true} />
if (!srv || !serviceInfo || filtersList.isError) return <Navigate to="/" replace /> if (!srv || !serviceInfo || filtersList.isError) return <Navigate to="/" replace />
@@ -101,6 +124,8 @@ export default function ServiceDetailsNFProxy() {
</Box> </Box>
{isMedium?null:<Space h="md" />} {isMedium?null:<Space h="md" />}
<Box className='center-flex'> <Box className='center-flex'>
<ExceptionWarning service_id={srv} />
<Space w="sm" />
<Badge color={status_color} radius="md" size="xl" variant="filled" mr="sm"> <Badge color={status_color} radius="md" size="xl" variant="filled" mr="sm">
{serviceInfo.status} {serviceInfo.status}
</Badge> </Badge>
@@ -115,7 +140,13 @@ export default function ServiceDetailsNFProxy() {
<Divider /> <Divider />
<Menu.Label><b>Danger zone</b></Menu.Label> <Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item> <Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</MenuDropDownWithButton> </MenuDropDownWithButton>
<Space w="md"/>
<Tooltip label="Show logs" zIndex={0} color="cyan">
<ActionIcon color="cyan" size="lg" radius="md" onClick={()=>setOpenLogModal(true)} loading={buttonLoading} variant="filled">
<FiFileText size="20px" />
</ActionIcon>
</Tooltip>
</Box> </Box>
</Box> </Box>
{isMedium?null:<Space h="md" />} {isMedium?null:<Space h="md" />}
@@ -133,23 +164,19 @@ export default function ServiceDetailsNFProxy() {
</Box> </Box>
{isMedium?null:<Space h="xl" />} {isMedium?null:<Space h="xl" />}
<Box className='center-flex'> <Box className='center-flex'>
<Tooltip label="Go back" zIndex={0} color="cyan" opened={tooltipBackOpened}> <Tooltip label="Go back" zIndex={0} color="cyan">
<ActionIcon color="cyan" <ActionIcon color="cyan"
onClick={() => navigate("/")} size="xl" radius="md" variant="filled" onClick={() => navigate("/")} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-back-id" aria-describedby="tooltip-back-id">
onFocus={() => setTooltipBackOpened(false)} onBlur={() => setTooltipBackOpened(false)}
onMouseEnter={() => setTooltipBackOpened(true)} onMouseLeave={() => setTooltipBackOpened(false)}>
<FaArrowLeft size="25px" /> <FaArrowLeft size="25px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Space w="md"/> <Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}> <Tooltip label="Stop service" zIndex={0} color="red">
<ActionIcon color="red" loading={buttonLoading} <ActionIcon color="red" loading={buttonLoading}
onClick={stopService} size="xl" radius="md" variant="filled" onClick={stopService} size="xl" radius="md" variant="filled"
disabled={serviceInfo.status === "stop"} disabled={serviceInfo.status === "stop"}
aria-describedby="tooltip-stop-id" aria-describedby="tooltip-stop-id">
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
<FaStop size="20px" /> <FaStop size="20px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
@@ -177,7 +204,7 @@ export default function ServiceDetailsNFProxy() {
<Title className='center-flex' style={{textAlign:"center"}} order={3}>Install the firegex client:<Space w="xs" /><Code mb={-4} >pip install fgex</Code></Title> <Title className='center-flex' style={{textAlign:"center"}} order={3}>Install the firegex client:<Space w="xs" /><Code mb={-4} >pip install fgex</Code></Title>
<Space h="xs" /> <Space h="xs" />
<Title className='center-flex' style={{textAlign:"center"}} order={3}>Then run the command:<Space w="xs" /><Code mb={-4} >fgex nfproxy</Code></Title> <Title className='center-flex' style={{textAlign:"center"}} order={3}>Then run the command:<Space w="xs" /><Code mb={-4} >fgex nfproxy</Code></Title>
</>:<>{filtersList.data?.map( (filterInfo) => <PyFilterView filterInfo={filterInfo} />)}</> </>:<>{filtersList.data?.map( (filterInfo) => <PyFilterView filterInfo={filterInfo} key={filterInfo.name}/>)}</>
} }
<YesNoModal <YesNoModal
title='Are you sure to delete this service?' title='Are you sure to delete this service?'
@@ -196,5 +223,11 @@ export default function ServiceDetailsNFProxy() {
onClose={()=>setEditModal(false)} onClose={()=>setEditModal(false)}
edit={serviceInfo} edit={serviceInfo}
/> />
<ModalLog
opened={openLogModal}
close={()=>setOpenLogModal(false)}
title={`Logs for service ${serviceInfo.name}`}
data={logData.join("")}
/>
</> </>
} }

View File

@@ -20,9 +20,6 @@ export default function NFProxy({ children }: { children: any }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const {srv} = useParams() const {srv} = useParams()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
const services = nfproxyServiceQuery() const services = nfproxyServiceQuery()
const fileDialog = useFileDialog({ const fileDialog = useFileDialog({
@@ -116,27 +113,22 @@ export default function NFProxy({ children }: { children: any }) {
{isMedium?null:<Space h="md" />} {isMedium?null:<Space h="md" />}
<Box className='center-flex' > <Box className='center-flex' >
{ srv? { srv?
<Tooltip label="Upload a new filter code" position='bottom' color="blue" opened={tooltipAddOpened}> <Tooltip label="Upload a new filter code" position='bottom' color="blue">
<ActionIcon <ActionIcon color="blue" size="lg" radius="md" variant="filled" onClick={fileDialog.open}>
color="blue" size="lg" radius="md" variant="filled" <MdUploadFile size={18} />
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)}
onMouseLeave={() => setTooltipAddOpened(false)} onClick={fileDialog.open}>
<MdUploadFile size={18} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
: <Tooltip label="Add a new service" position='bottom' color="blue" opened={tooltipAddOpened}> : <Tooltip label="Add a new service" position='bottom' color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled">
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} <BsPlusLg size={18} />
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
} }
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}> <Tooltip label="Refresh" position='bottom' color="indigo">
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["nfproxy"])} size="lg" radius="md" variant="filled" <ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["nfproxy"])} size="lg" radius="md" variant="filled" loading={services.isFetching}>
loading={services.isFetching} <TbReload size={18} />
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} </ActionIcon>
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</Box> </Box>
@@ -148,10 +140,10 @@ export default function NFProxy({ children }: { children: any }) {
navigator("/nfproxy/"+srv.service_id) navigator("/nfproxy/"+srv.service_id)
}} />):<><Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No services found! Add one clicking the "+" buttons</Title> }} />):<><Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No services found! Add one clicking the "+" buttons</Title>
<Box className='center-flex'> <Box className='center-flex'>
<Tooltip label="Add a new service" color="blue" opened={tooltipAddServOpened}> <Tooltip label="Add a new service" color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled">
onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)} <BsPlusLg size="20px" />
onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}><BsPlusLg size="20px" /></ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</>} </>}

View File

@@ -27,15 +27,12 @@ export default function ServiceDetailsNFRegex() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const services = nfregexServiceQuery() const services = nfregexServiceQuery()
const serviceInfo = services.data?.find(s => s.service_id == srv) const serviceInfo = services.data?.find(s => s.service_id == srv)
const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false)
const regexesList = nfregexServiceRegexesQuery(srv??"") const regexesList = nfregexServiceRegexesQuery(srv??"")
const [deleteModal, setDeleteModal] = useState(false) const [deleteModal, setDeleteModal] = useState(false)
const [renameModal, setRenameModal] = useState(false) const [renameModal, setRenameModal] = useState(false)
const [editModal, setEditModal] = useState(false) const [editModal, setEditModal] = useState(false)
const [buttonLoading, setButtonLoading] = useState(false) const [buttonLoading, setButtonLoading] = useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [tooltipBackOpened, setTooltipBackOpened] = useState(false);
const navigate = useNavigate() const navigate = useNavigate()
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
@@ -133,23 +130,19 @@ export default function ServiceDetailsNFRegex() {
</Box> </Box>
{isMedium?null:<Space h="xl" />} {isMedium?null:<Space h="xl" />}
<Box className='center-flex'> <Box className='center-flex'>
<Tooltip label="Go back" zIndex={0} color="cyan" opened={tooltipBackOpened}> <Tooltip label="Go back" zIndex={0} color="cyan">
<ActionIcon color="cyan" <ActionIcon color="cyan"
onClick={() => navigate("/")} size="xl" radius="md" variant="filled" onClick={() => navigate("/")} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-back-id" aria-describedby="tooltip-back-id">
onFocus={() => setTooltipBackOpened(false)} onBlur={() => setTooltipBackOpened(false)}
onMouseEnter={() => setTooltipBackOpened(true)} onMouseLeave={() => setTooltipBackOpened(false)}>
<FaArrowLeft size="25px" /> <FaArrowLeft size="25px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Space w="md"/> <Space w="md"/>
<Tooltip label="Stop service" zIndex={0} color="red" opened={tooltipStopOpened}> <Tooltip label="Stop service" zIndex={0} color="red">
<ActionIcon color="red" loading={buttonLoading} <ActionIcon color="red" loading={buttonLoading}
onClick={stopService} size="xl" radius="md" variant="filled" onClick={stopService} size="xl" radius="md" variant="filled"
disabled={serviceInfo.status === "stop"} disabled={serviceInfo.status === "stop"}
aria-describedby="tooltip-stop-id" aria-describedby="tooltip-stop-id">
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
<FaStop size="20px" /> <FaStop size="20px" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
@@ -168,11 +161,9 @@ export default function ServiceDetailsNFRegex() {
<Title className='center-flex' style={{textAlign:"center"}} order={3}>No regex found for this service! Add one by clicking the "+" buttons</Title> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No regex found for this service! Add one by clicking the "+" buttons</Title>
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
<Box className='center-flex'> <Box className='center-flex'>
<Tooltip label="Add a new regex" zIndex={0} color="blue" opened={tooltipAddRegexOpened}> <Tooltip label="Add a new regex" zIndex={0} color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-AddRegex-id" aria-describedby="tooltip-AddRegex-id"><BsPlusLg size="20px" /></ActionIcon>
onFocus={() => setTooltipAddRegexOpened(false)} onBlur={() => setTooltipAddRegexOpened(false)}
onMouseEnter={() => setTooltipAddRegexOpened(true)} onMouseLeave={() => setTooltipAddRegexOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</>: </>:

View File

@@ -19,9 +19,6 @@ function NFRegex({ children }: { children: any }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const {srv} = useParams() const {srv} = useParams()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
const services = nfregexServiceQuery() const services = nfregexServiceQuery()
@@ -50,23 +47,17 @@ function NFRegex({ children }: { children: any }) {
{isMedium?null:<Space h="md" />} {isMedium?null:<Space h="md" />}
<Box className='center-flex' > <Box className='center-flex' >
{ srv? { srv?
<Tooltip label="Add a new regex" position='bottom' color="blue" opened={tooltipAddOpened}> <Tooltip label="Add a new regex" position='bottom' color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"><BsPlusLg size={18} /></ActionIcon>
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
</Tooltip> </Tooltip>
: <Tooltip label="Add a new service" position='bottom' color="blue" opened={tooltipAddOpened}> : <Tooltip label="Add a new service" position='bottom' color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"><BsPlusLg size={18} /></ActionIcon>
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
</Tooltip> </Tooltip>
} }
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}> <Tooltip label="Refresh" position='bottom' color="indigo">
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["nfregex"])} size="lg" radius="md" variant="filled" <ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["nfregex"])} size="lg" radius="md" variant="filled"
loading={services.isFetching} loading={services.isFetching}><TbReload size={18} /></ActionIcon>
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</Box> </Box>
@@ -78,10 +69,8 @@ function NFRegex({ children }: { children: any }) {
navigator("/nfregex/"+srv.service_id) navigator("/nfregex/"+srv.service_id)
}} />):<><Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No services found! Add one clicking the "+" buttons</Title> }} />):<><Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No services found! Add one clicking the "+" buttons</Title>
<Box className='center-flex'> <Box className='center-flex'>
<Tooltip label="Add a new service" color="blue" opened={tooltipAddServOpened}> <Tooltip label="Add a new service" color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"><BsPlusLg size="20px" /></ActionIcon>
onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)}
onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</>} </>}

View File

@@ -14,10 +14,7 @@ import { GrDirections } from 'react-icons/gr';
function PortHijack() { function PortHijack() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
const isMedium = isMediumScreen() const isMedium = isMediumScreen()
const services = porthijackServiceQuery() const services = porthijackServiceQuery()
@@ -37,17 +34,13 @@ function PortHijack() {
<Box className='center-flex'> <Box className='center-flex'>
<Badge size="md" radius="sm" color="yellow" variant="filled"><FaServer style={{ marginBottom: -1, marginRight: 4}} />Services: {services.isLoading?0:services.data?.length}</Badge> <Badge size="md" radius="sm" color="yellow" variant="filled"><FaServer style={{ marginBottom: -1, marginRight: 4}} />Services: {services.isLoading?0:services.data?.length}</Badge>
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Add a new service" position='bottom' color="blue" opened={tooltipAddOpened}> <Tooltip label="Add a new service" position='bottom' color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"><BsPlusLg size={18} /></ActionIcon>
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
</Tooltip> </Tooltip>
<Space w="xs" /> <Space w="xs" />
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}> <Tooltip label="Refresh" position='bottom' color="indigo">
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["porthijack"])} size="lg" radius="md" variant="filled" <ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["porthijack"])} size="lg" radius="md" variant="filled"
loading={services.isFetching} loading={services.isFetching}><TbReload size={18} /></ActionIcon>
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</Box> </Box>
@@ -57,10 +50,8 @@ function PortHijack() {
{(services.data && services.data.length > 0) ?services.data.map( srv => <ServiceRow service={srv} key={srv.service_id} />):<> {(services.data && services.data.length > 0) ?services.data.map( srv => <ServiceRow service={srv} key={srv.service_id} />):<>
<Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No services found! Add one clicking the "+" buttons</Title> <Space h="xl"/> <Title className='center-flex' style={{textAlign:"center"}} order={3}>No services found! Add one clicking the "+" buttons</Title>
<Box className='center-flex'> <Box className='center-flex'>
<Tooltip label="Add a new service" color="blue" opened={tooltipAddServOpened}> <Tooltip label="Add a new service" color="blue">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled" <ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"><BsPlusLg size="20px" /></ActionIcon>
onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)}
onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip> </Tooltip>
</Box> </Box>
</>} </>}

View File

@@ -35,7 +35,7 @@ else:
def exit_test(code): def exit_test(code):
if service_id: if service_id:
server.stop() server.kill()
if(firegex.nf_delete_service(service_id)): if(firegex.nf_delete_service(service_id)):
puts("Sucessfully deleted service ✔", color=colors.green) puts("Sucessfully deleted service ✔", color=colors.green)
else: else:

View File

@@ -27,5 +27,10 @@ python3 ph_test.py -p $PASSWORD -m udp || ERROR=1
echo "Running Port Hijack UDP ipv6" echo "Running Port Hijack UDP ipv6"
python3 ph_test.py -p $PASSWORD -m udp -6 || ERROR=1 python3 ph_test.py -p $PASSWORD -m udp -6 || ERROR=1
if [[ "$ERROR" == "0" ]] then
python3 benchmark.py -p $PASSWORD -r 5 -d 1 -s 10 || ERROR=1
fi
exit $ERROR exit $ERROR