diff --git a/Dockerfile b/Dockerfile index feb8659..4599907 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN bun run build FROM --platform=$TARGETARCH registry.fedoraproject.org/fedora:latest 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 \ - 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 WORKDIR /execute diff --git a/backend/app.py b/backend/app.py index f12224c..1869b97 100644 --- a/backend/app.py +++ b/backend/app.py @@ -219,7 +219,7 @@ if __name__ == '__main__': os.chdir(os.path.dirname(os.path.realpath(__file__))) uvicorn.run( "app:app", - host="::" if DEBUG else None, + host="0.0.0.0" if DEBUG else None, port=FIREGEX_PORT, reload=DEBUG and not NORELOAD, access_log=True, diff --git a/backend/binsrc/classes/nfqueue.cpp b/backend/binsrc/classes/nfqueue.cpp index 7bfe9c4..36d4d9e 100644 --- a/backend/binsrc/classes/nfqueue.cpp +++ b/backend/binsrc/classes/nfqueue.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace std; @@ -17,7 +18,17 @@ enum class FilterAction{ DROP, ACCEPT, MANGLE, NOACTION }; enum class L4Proto { TCP, UDP, RAW }; 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 tcp_ack_map; template class PktRequest { @@ -28,6 +39,7 @@ class PktRequest { uint32_t packet_id; size_t _original_size; size_t _data_original_size; + size_t _header_size; bool need_tcp_fixing = false; public: bool is_ipv6; @@ -39,18 +51,15 @@ class PktRequest { bool is_input; string packet; - char* data; - size_t data_size; stream_id sid; - int64_t* tcp_in_offset = nullptr; - int64_t* tcp_out_offset = nullptr; + tcp_ack_seq_ctx* ack_seq_offset = nullptr; - T* ctx; + T* ctx = nullptr; private: - static size_t inner_data_size(Tins::PDU* pdu){ + static inline size_t inner_data_size(Tins::PDU* pdu){ if (pdu == nullptr){ return 0; } @@ -61,9 +70,9 @@ class PktRequest { return inner->size(); } - inline void fetch_data_size(Tins::PDU* pdu){ - data_size = inner_data_size(pdu); - _data_original_size = data_size; + inline void __internal_fetch_data_size(Tins::PDU* pdu){ + _data_original_size = inner_data_size(pdu); + _header_size = _original_size - _data_original_size; } L4Proto fill_l4_info(){ @@ -72,14 +81,14 @@ class PktRequest { if (tcp == nullptr){ udp = ipv6->find_pdu(); if (udp == nullptr){ - fetch_data_size(ipv6); + __internal_fetch_data_size(ipv6); return L4Proto::RAW; }else{ - fetch_data_size(udp); + __internal_fetch_data_size(udp); return L4Proto::UDP; } }else{ - fetch_data_size(tcp); + __internal_fetch_data_size(tcp); return L4Proto::TCP; } }else{ @@ -87,73 +96,23 @@ class PktRequest { if (tcp == nullptr){ udp = ipv4->find_pdu(); if (udp == nullptr){ - fetch_data_size(ipv4); + __internal_fetch_data_size(ipv4); return L4Proto::RAW; }else{ - fetch_data_size(udp); + __internal_fetch_data_size(udp); return L4Proto::UDP; } }else{ - fetch_data_size(tcp); + __internal_fetch_data_size(tcp); return L4Proto::TCP; } } } 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(); - } - return ipv6_new.serialize(); - }else{ - Tins::IP ipv4_new = Tins::IP(data, data_size); - if (tcp){ - Tins::TCP* tcp_new = ipv4_new.find_pdu(); - } - 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: 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); _original_size = ipv6->size(); }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); _original_size = ipv4->size(); } l4_proto = fill_l4_info(); - data = packet.data()+(plen-data_size); #ifdef DEBUG 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 } - 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){ - _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(); + data_pdu = tcp; + }else if (udp){ + udp = ipv6->find_pdu(); + 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(); + data_pdu = tcp; + }else if(udp){ + udp = ipv4->find_pdu(); + 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(){ return _original_size; } @@ -225,11 +287,11 @@ class PktRequest { void reject(){ if (tcp){ //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 //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 - if (_data_original_size != 0 && is_input){ + if (_data_original_size != 0){ tcp->set_flag(Tins::TCP::FIN,1); tcp->set_flag(Tins::TCP::ACK,1); 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){ - action = FilterAction::MANGLE; - perfrom_action(pkt, pkt_size); + try{ + set_packet(raw_pkt, raw_pkt_size); + reserialize(); + action = FilterAction::MANGLE; + }catch(...){ + action = FilterAction::DROP; + } + perfrom_action(false); }else{ throw invalid_argument("Cannot mangle a packet that has already been accepted or dropped"); } @@ -259,7 +327,7 @@ class PktRequest { delete ipv6; } - inline Tins::PDU::serialization_type serialize(){ + Tins::PDU::serialization_type serialize(){ if (is_ipv6){ return ipv6->serialize(); }else{ @@ -268,15 +336,17 @@ class PktRequest { } 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]; struct nlmsghdr *nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(res_id)); switch (action) { case FilterAction::ACCEPT: if (need_tcp_fixing){ - Tins::PDU::serialization_type data = serialize(); - nfq_nlmsg_verdict_put_pkt(nlh_verdict, data.data(), data.size()); + if (do_serialize){ + reserialize(); + } + nfq_nlmsg_verdict_put_pkt(nlh_verdict, packet.data(), packet.size()); } nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT ); break; @@ -285,32 +355,20 @@ class PktRequest { break; case FilterAction::MANGLE:{ //If not custom data, use the data in the packets - Tins::PDU::serialization_type data; - if (custom_data == nullptr){ - 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; - } + if(do_serialize){ + reserialize(); } + nfq_nlmsg_verdict_put_pkt(nlh_verdict, packet.data(), packet.size()); #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: " << new_size << "]" << endl; + cerr << "[DEBUG] MANGLEDPKT " << (is_input?"-> IN ":"<- OUT") << " [SIZE: " << packet.size()-header_size() << "]" << endl; #endif - if (tcp && custom_data_size != _original_size){ - int64_t delta = ((int64_t)custom_data_size) - ((int64_t)_original_size); - - if (is_input && tcp_in_offset != nullptr){ - *tcp_in_offset += delta; - }else if (!is_input && tcp_out_offset != nullptr){ - *tcp_out_offset += delta; + if (tcp && ack_seq_offset && packet.size() != _original_size){ + if (is_input){ + ack_seq_offset->in += packet.size() - _original_size; + }else{ + ack_seq_offset->out += packet.size() - _original_size; } } - nfq_nlmsg_verdict_put_pkt(nlh_verdict, data.data(), data.size()); nfq_nlmsg_verdict_put(nlh_verdict, ntohl(packet_id), NF_ACCEPT ); break; } diff --git a/backend/binsrc/pyproxy/pyproxy.cpp b/backend/binsrc/pyproxy/pyproxy.cpp index 41f4540..93e7714 100644 --- a/backend/binsrc/pyproxy/pyproxy.cpp +++ b/backend/binsrc/pyproxy/pyproxy.cpp @@ -16,6 +16,7 @@ #include #include #include "../classes/netfilter.cpp" +#include "../classes/nfqueue.cpp" #include "stream_ctx.cpp" #include "settings.cpp" #include @@ -46,7 +47,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue { }; PyThreadState *tstate = NULL; NfQueue::PktRequest* pkt; - tcp_ack_seq_ctx* current_tcp_ack = nullptr; + NfQueue::tcp_ack_seq_ctx* current_tcp_ack = nullptr; void before_loop() override { PyStatus pystatus; @@ -89,7 +90,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue { pyq->pkt->drop();// This is needed because the callback has to take the updated pkt pointer! } - void filter_action(NfQueue::PktRequest* pkt, Stream& stream){ + void filter_action(NfQueue::PktRequest* pkt, Stream& stream, const string& data){ auto stream_search = sctx.streams_ctx.find(pkt->sid); pyfilter_ctx* stream_match; if (stream_search == sctx.streams_ctx.end()){ @@ -108,7 +109,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue { stream_match = stream_search->second; } - auto result = stream_match->handle_packet(pkt); + auto result = stream_match->handle_packet(pkt, data); switch(result.action){ case PyFilterResponse::ACCEPT: return pkt->accept(); @@ -125,7 +126,7 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue { stream.server_data_callback(bind(keep_fin_packet, this)); return pkt->reject(); 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){ 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); @@ -146,20 +147,21 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue { } - static void on_data_recv(Stream& stream, PyProxyQueue* proxy_info, string data) { - proxy_info->pkt->data = data.data(); - proxy_info->pkt->data_size = data.size(); - proxy_info->filter_action(proxy_info->pkt, stream); + static void on_data_recv(Stream& stream, PyProxyQueue* pyq, const string& data) { + pyq->pkt->fix_data_payload(); + pyq->filter_action(pyq->pkt, stream, data); //Only here the rebuilt_tcp_data is set } //Input data filtering - static void on_client_data(Stream& stream, PyProxyQueue* proxy_info) { - on_data_recv(stream, proxy_info, string(stream.client_payload().begin(), stream.client_payload().end())); + static void on_client_data(Stream& stream, PyProxyQueue* pyq) { + auto data = stream.client_payload(); + on_data_recv(stream, pyq, string((char*)data.data(), data.size())); } //Server data filtering - static void on_server_data(Stream& stream, PyProxyQueue* proxy_info) { - on_data_recv(stream, proxy_info, string(stream.server_payload().begin(), stream.server_payload().end())); + static void on_server_data(Stream& stream, PyProxyQueue* pyq) { + 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 @@ -178,10 +180,9 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue { if (pyq->current_tcp_ack != nullptr){ pyq->current_tcp_ack->reset(); }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->pkt->tcp_in_offset = &pyq->current_tcp_ack->in_tcp_offset; - pyq->pkt->tcp_out_offset = &pyq->current_tcp_ack->out_tcp_offset; + pyq->pkt->ack_seq_offset = pyq->current_tcp_ack; // Set ack context } //Should not happen, but with this we can be sure about this @@ -205,18 +206,17 @@ class PyProxyQueue: public NfQueue::ThreadNfQueue { auto tcp_ack_search = sctx.tcp_ack_ctx.find(pkt->sid); if (tcp_ack_search != sctx.tcp_ack_ctx.end()){ current_tcp_ack = tcp_ack_search->second; - pkt->tcp_in_offset = ¤t_tcp_ack->in_tcp_offset; - pkt->tcp_out_offset = ¤t_tcp_ack->out_tcp_offset; + pkt->ack_seq_offset = current_tcp_ack; }else{ current_tcp_ack = nullptr; //If necessary will be created by libtis new_stream callback } + pkt->fix_tcp_ack(); + if (pkt->is_ipv6){ - pkt->fix_tcp_ack(); follower.process_packet(*pkt->ipv6); }else{ - pkt->fix_tcp_ack(); follower.process_packet(*pkt->ipv4); } diff --git a/backend/binsrc/pyproxy/stream_ctx.cpp b/backend/binsrc/pyproxy/stream_ctx.cpp index 761e20d..ba7db5c 100644 --- a/backend/binsrc/pyproxy/stream_ctx.cpp +++ b/backend/binsrc/pyproxy/stream_ctx.cpp @@ -7,7 +7,9 @@ #include #include #include "../classes/netfilter.cpp" +#include "../classes/nfqueue.cpp" #include "settings.cpp" +#include "../utils.cpp" using namespace std; @@ -50,17 +52,6 @@ struct py_filter_response { 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 { PyObject * glob = nullptr; @@ -105,12 +96,14 @@ struct pyfilter_ctx { } py_filter_response handle_packet( - NfQueue::PktRequest* pkt + NfQueue::PktRequest* pkt, + const string& data ){ PyObject * packet_info = PyDict_New(); - - set_item_to_dict(packet_info, "data", PyBytes_FromStringAndSize(pkt->data, pkt->data_size)); - set_item_to_dict(packet_info, "l4_size", PyLong_FromLong(pkt->data_original_size())); + + pkt->reserialize(); + 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, "is_input", PyBool_FromLong(pkt->is_input)); set_item_to_dict(packet_info, "is_ipv6", PyBool_FromLong(pkt->is_ipv6)); @@ -129,8 +122,7 @@ struct pyfilter_ctx { #endif return py_filter_response(PyFilterResponse::EXCEPTION); } - - + Py_DECREF(result); result = get_item_from_glob("__firegex_pyfilter_result"); @@ -235,11 +227,12 @@ struct pyfilter_ctx { }; typedef map matching_map; -typedef map tcp_ack_map; + struct stream_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){ auto stream_search = streams_ctx.find(sid); diff --git a/backend/binsrc/regex/regexfilter.cpp b/backend/binsrc/regex/regexfilter.cpp index c84b12b..f50616e 100644 --- a/backend/binsrc/regex/regexfilter.cpp +++ b/backend/binsrc/regex/regexfilter.cpp @@ -20,6 +20,7 @@ #include "../classes/netfilter.cpp" #include "stream_ctx.cpp" #include "regex_rules.cpp" +#include "../utils.cpp" using namespace std; @@ -30,8 +31,6 @@ namespace Regex { using Tins::TCPIP::Stream; using Tins::TCPIP::StreamFollower; - - class RegexNfQueue : public NfQueue::ThreadNfQueue { public: stream_ctx sctx; @@ -39,7 +38,7 @@ public: StreamFollower follower; NfQueue::PktRequest* pkt; - bool filter_action(NfQueue::PktRequest* pkt){ + bool filter_action(NfQueue::PktRequest* pkt, const string& data){ shared_ptr conf = regex_config; auto current_version = conf->ver(); @@ -85,12 +84,12 @@ public: stream_match = stream_search->second; } 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 ); }else{ err = hs_scan( - regex_matcher,pkt->data, pkt->data_size, + regex_matcher, data.c_str(), data.size(), 0, scratch_space, match_func, &match_res ); } @@ -102,7 +101,7 @@ public: throw invalid_argument("Cannot close stream match on hyperscan"); } 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"); } if (match_res.has_matched){ @@ -113,41 +112,13 @@ public: return true; } - void handle_next_packet(NfQueue::PktRequest* _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 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) { - nfq->pkt->data = data.data(); - nfq->pkt->data_size = data.size(); - if (!nfq->filter_action(nfq->pkt)){ + static void on_data_recv(Stream& stream, RegexNfQueue* nfq, const string& data) { + if (!nfq->filter_action(nfq->pkt, data)){ nfq->sctx.clean_stream_by_id(nfq->pkt->sid); stream.client_data_callback(bind(keep_fin_packet, nfq)); stream.server_data_callback(bind(keep_fin_packet, nfq)); @@ -157,12 +128,14 @@ public: //Input data filtering 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 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 @@ -181,6 +154,32 @@ public: stream.stream_closed_callback(bind(on_stream_close, placeholders::_1, nfq)); } + void handle_next_packet(NfQueue::PktRequest* _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{ follower.new_stream_callback(bind(on_new_stream, placeholders::_1, this)); follower.stream_termination_callback(bind(on_stream_close, placeholders::_1, this)); diff --git a/backend/modules/nfproxy/firegex.py b/backend/modules/nfproxy/firegex.py index 13eea5a..22c189a 100644 --- a/backend/modules/nfproxy/firegex.py +++ b/backend/modules/nfproxy/firegex.py @@ -5,9 +5,12 @@ import asyncio import traceback from fastapi import HTTPException import time +from utils import run_func nft = FiregexTables() +OUTSTREAM_BUFFER_SIZE = 1024*10 + class FiregexInterceptor: def __init__(self): @@ -28,14 +31,20 @@ class FiregexInterceptor: self.sock_writer:asyncio.StreamWriter = None self.sock_conn_lock:asyncio.Lock self.last_time_exception = 0 + self.outstrem_function = None + self.expection_function = None + self.outstrem_task: asyncio.Task + self.outstrem_buffer = "" @classmethod - async def start(cls, srv: Service): + async def start(cls, srv: Service, outstream_func=None, exception_func=None): self = cls() self.srv = srv self.filter_map_lock = asyncio.Lock() self.update_config_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(): await self.sock_conn_lock.acquire() self.sock_path = f"/tmp/firegex_nfproxy_{srv.id}.sock" @@ -50,16 +59,37 @@ class FiregexInterceptor: await self.ack_lock.acquire() 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): proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cpproxy") self.process = await asyncio.create_subprocess_exec( proxy_binary_path, stdin=asyncio.subprocess.DEVNULL, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, env={ "NTHREADS": os.getenv("NTHREADS","1"), "FIREGEX_NFQUEUE_FAIL_OPEN": "1" if self.srv.fail_open else "0", "FIREGEX_NFPROXY_SOCK": self.sock_path }, ) + self.outstrem_task = asyncio.create_task(self._stream_handler()) try: async with asyncio.timeout(3): await self.sock_conn_lock.acquire() @@ -101,9 +131,7 @@ class FiregexInterceptor: filter_name = line.split()[1] print("BLOCKED", filter_name) async with self.filter_map_lock: - print("LOCKED MAP LOCK") if filter_name in self.filter_map: - print("ADDING BLOCKED PACKET") self.filter_map[filter_name].blocked_packets+=1 await self.filter_map[filter_name].update() if line.startswith("MANGLED "): @@ -113,8 +141,9 @@ class FiregexInterceptor: self.filter_map[filter_name].edited_packets+=1 await self.filter_map[filter_name].update() if line.startswith("EXCEPTION"): - self.last_time_exception = time.time() - print("TODO EXCEPTION HANDLING") # TODO + self.last_time_exception = int(time.time()*1000) #ms timestamp + if self.expection_function: + await run_func(self.expection_function, self.srv.id, self.last_time_exception) if line.startswith("ACK "): self.ack_arrived = True self.ack_status = line.split()[1].upper() == "OK" @@ -132,6 +161,7 @@ class FiregexInterceptor: self.server_task.cancel() self.update_task.cancel() self.unix_sock.close() + self.outstrem_task.cancel() if os.path.exists(self.sock_path): os.remove(self.sock_path) if self.process and self.process.returncode is None: diff --git a/backend/modules/nfproxy/firewall.py b/backend/modules/nfproxy/firewall.py index c424686..045f9ab 100644 --- a/backend/modules/nfproxy/firewall.py +++ b/backend/modules/nfproxy/firewall.py @@ -3,6 +3,7 @@ from modules.nfproxy.firegex import FiregexInterceptor from modules.nfproxy.nftables import FiregexTables, FiregexFilter from modules.nfproxy.models import Service, PyFilter from utils.sqlite import SQLite +from utils import run_func class STATUS: STOP = "stop" @@ -11,13 +12,20 @@ class STATUS: nft = FiregexTables() class ServiceManager: - def __init__(self, srv: Service, db): + def __init__(self, srv: Service, db, outstream_func=None, exception_func=None): self.srv = srv self.db = db self.status = STATUS.STOP self.filters: dict[str, FiregexFilter] = {} self.lock = asyncio.Lock() 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): pyfilters = [ @@ -52,10 +60,16 @@ class ServiceManager: self.status = 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): if not self.interceptor: 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() self._set_status(STATUS.ACTIVE) @@ -75,10 +89,12 @@ class ServiceManager: await self._update_filters_from_db() class FirewallManager: - def __init__(self, db:SQLite): + def __init__(self, db:SQLite, outstream_func=None, exception_func=None): self.db = db self.service_table: dict[str, ServiceManager] = {} self.lock = asyncio.Lock() + self.outstream_function = outstream_func + self.exception_function = exception_func async def close(self): for key in list(self.service_table.keys()): @@ -100,7 +116,7 @@ class FirewallManager: srv = Service.from_dict(srv) if srv.id in self.service_table: 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) def get(self,srv_id) -> ServiceManager: diff --git a/backend/modules/nfproxy/nftables.py b/backend/modules/nfproxy/nftables.py index 84c24c9..5bb1050 100644 --- a/backend/modules/nfproxy/nftables.py +++ b/backend/modules/nfproxy/nftables.py @@ -28,22 +28,22 @@ class FiregexTables(NFTableManager): def __init__(self): super().__init__([ - {"add":{"chain":{ + {"add":{"chain":{ #Input chain attached before conntrack see it "family":"inet", "table":self.table_name, "name":self.input_chain, "type":"filter", "hook":"prerouting", - "prio":-150, + "prio":-301, "policy":"accept" }}}, - {"add":{"chain":{ + {"add":{"chain":{ #Output chain attached after conntrack saw it "family":"inet", "table":self.table_name, "name":self.output_chain, "type":"filter", "hook":"postrouting", - "prio":-150, + "prio":-290, "policy":"accept" }}} ],[ diff --git a/backend/modules/nfregex/nftables.py b/backend/modules/nfregex/nftables.py index 34ed844..c352226 100644 --- a/backend/modules/nfregex/nftables.py +++ b/backend/modules/nfregex/nftables.py @@ -26,7 +26,7 @@ class FiregexTables(NFTableManager): "name":self.input_chain, "type":"filter", "hook":"prerouting", - "prio":-150, + "prio":-301, "policy":"accept" }}}, {"add":{"chain":{ @@ -35,7 +35,7 @@ class FiregexTables(NFTableManager): "name":self.output_chain, "type":"filter", "hook":"postrouting", - "prio":-150, + "prio":-301, "policy":"accept" }}} ],[ diff --git a/backend/modules/porthijack/nftables.py b/backend/modules/porthijack/nftables.py index 1d8dcde..0590b2f 100644 --- a/backend/modules/porthijack/nftables.py +++ b/backend/modules/porthijack/nftables.py @@ -28,7 +28,7 @@ class FiregexTables(NFTableManager): "name":self.prerouting_porthijack, "type":"filter", "hook":"prerouting", - "prio":-300, + "prio":-310, "policy":"accept" }}}, {"add":{"chain":{ @@ -37,7 +37,7 @@ class FiregexTables(NFTableManager): "name":self.postrouting_porthijack, "type":"filter", "hook":"postrouting", - "prio":-300, + "prio":-310, "policy":"accept" }}} ],[ diff --git a/backend/routers/nfproxy.py b/backend/routers/nfproxy.py index 77405d1..96fffa2 100644 --- a/backend/routers/nfproxy.py +++ b/backend/routers/nfproxy.py @@ -14,6 +14,7 @@ from modules.nfproxy.nftables import convert_protocol_to_l4 import asyncio import traceback from utils import DEBUG +import utils class ServiceModel(BaseModel): service_id: str @@ -107,6 +108,10 @@ async def startup(): await firewall.init() except Exception as 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(): db.backup() @@ -121,7 +126,13 @@ def gen_service_id(): break 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]) async def get_service_list(): @@ -355,3 +366,33 @@ async def get_pyfilters(service_id: str): return f.read() except FileNotFoundError: 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}") + diff --git a/backend/utils/loader.py b/backend/utils/loader.py index 5e5dd32..d13b9d3 100644 --- a/backend/utils/loader.py +++ b/backend/utils/loader.py @@ -8,6 +8,7 @@ from fastapi.responses import FileResponse from utils import DEBUG, ON_DOCKER, ROUTERS_DIR, list_files, run_func from utils.models import ResetRequest import asyncio +import traceback REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/" REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html") @@ -70,6 +71,7 @@ def get_router_modules(): name=route )) except Exception as e: + traceback.print_exc() print(f"Router {route} failed to load: {e}") return res diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a988fdc..375cdcd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,9 +5,8 @@ import { ImCross } from 'react-icons/im'; import { Outlet, Route, Routes } from 'react-router-dom'; import MainLayout from './components/MainLayout'; 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 io from 'socket.io-client'; import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails'; import PortHijack from './pages/PortHijack'; import { Firewall } from './pages/Firewall'; @@ -15,22 +14,6 @@ import { useQueryClient } from '@tanstack/react-query'; import NFProxy from './pages/NFProxy'; 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() { const [loading, setLoading] = useState(true); @@ -162,16 +145,16 @@ const PageRouting = ({ getStatus }:{ getStatus:()=>void }) => { useEffect(()=>{ getStatus() - socket.on("update", (data) => { + socketio.on("update", (data) => { queryClient.invalidateQueries({ queryKey: data }) }) - socket.on("connect_error", (err) => { + socketio.on("connect_error", (err) => { errorNotify("Socket.Io connection failed! ",`Error message: [${err.message}]`) getStatus() }); return () => { - socket.off("update") - socket.off("connect_error") + socketio.off("update") + socketio.off("connect_error") } },[]) diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index 938c8fb..02f5f6a 100644 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -31,8 +31,6 @@ function HeaderPage(props: any) { const [changePasswordModal, setChangePasswordModal] = useState(false); const [resetFiregexModal, setResetFiregexModal] = useState(false); - const [tooltipHomeOpened, setTooltipHomeOpened] = useState(false); - const [tooltipLogoutOpened,setTooltipLogoutOpened] = useState(false); return } onClick={() => setResetFiregexModal(true)}>Reset Firegex - + setTooltipHomeOpened(false)} onBlur={() => setTooltipHomeOpened(false)} - onMouseEnter={() => setTooltipHomeOpened(true)} onMouseLeave={() => setTooltipHomeOpened(false)}> + onClick={go_to_home}> - - setTooltipLogoutOpened(false)} onBlur={() => setTooltipLogoutOpened(false)} - onMouseEnter={() => setTooltipLogoutOpened(true)} onMouseLeave={() => setTooltipLogoutOpened(false)}> + + + setChangePasswordModal(false)} /> setResetFiregexModal(false)} /> diff --git a/frontend/src/components/ModalLog.tsx b/frontend/src/components/ModalLog.tsx new file mode 100644 index 0000000..d57c515 --- /dev/null +++ b/frontend/src/components/ModalLog.tsx @@ -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 + + {data} + + +} \ No newline at end of file diff --git a/frontend/src/components/NFProxy/ExceptionWarning.tsx b/frontend/src/components/NFProxy/ExceptionWarning.tsx new file mode 100644 index 0000000..64886b3 --- /dev/null +++ b/frontend/src/components/NFProxy/ExceptionWarning.tsx @@ -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(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)? + + + + :null} + +} \ No newline at end of file diff --git a/frontend/src/components/NFProxy/ServiceRow/index.tsx b/frontend/src/components/NFProxy/ServiceRow/index.tsx index 5943bb7..f90627e 100644 --- a/frontend/src/components/NFProxy/ServiceRow/index.tsx +++ b/frontend/src/components/NFProxy/ServiceRow/index.tsx @@ -15,6 +15,7 @@ import { FaFilter } from "react-icons/fa"; import { IoSettingsSharp } from 'react-icons/io5'; import AddEditService from '../AddEditService'; import { FaPencilAlt } from "react-icons/fa"; +import { ExceptionWarning } from '../ExceptionWarning'; 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 [buttonLoading, setButtonLoading] = useState(false) - const [tooltipStopOpened, setTooltipStopOpened] = useState(false); const [deleteModal, setDeleteModal] = useState(false) const [renameModal, setRenameModal] = useState(false) const [editModal, setEditModal] = useState(false) @@ -109,6 +109,8 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli {isMedium?:} + + Edit service } onClick={()=>setEditModal(true)}>Service Settings @@ -118,13 +120,11 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli } onClick={()=>setDeleteModal(true)}>Delete Service - + setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)} - onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}> + aria-describedby="tooltip-stop-id"> diff --git a/frontend/src/components/NFRegex/ServiceRow/index.tsx b/frontend/src/components/NFRegex/ServiceRow/index.tsx index 53a5c1b..9086d54 100644 --- a/frontend/src/components/NFRegex/ServiceRow/index.tsx +++ b/frontend/src/components/NFRegex/ServiceRow/index.tsx @@ -25,7 +25,6 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli const queryClient = useQueryClient() const [buttonLoading, setButtonLoading] = useState(false) - const [tooltipStopOpened, setTooltipStopOpened] = useState(false); const [deleteModal, setDeleteModal] = useState(false) const [renameModal, setRenameModal] = useState(false) const [editModal, setEditModal] = useState(false) @@ -115,13 +114,11 @@ export default function ServiceRow({ service, onClick }:{ service:Service, onCli } onClick={()=>setDeleteModal(true)}>Delete Service - + setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)} - onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}> + aria-describedby="tooltip-stop-id"> diff --git a/frontend/src/components/PortHijack/ServiceRow/index.tsx b/frontend/src/components/PortHijack/ServiceRow/index.tsx index 0b47b6b..7b29996 100644 --- a/frontend/src/components/PortHijack/ServiceRow/index.tsx +++ b/frontend/src/components/PortHijack/ServiceRow/index.tsx @@ -1,5 +1,5 @@ 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 { porthijack, Service } from '../utils'; import YesNoModal from '../../YesNoModal'; @@ -17,11 +17,9 @@ export default function ServiceRow({ service }:{ service:Service }) { let status_color = service.active ? "teal": "red" const [buttonLoading, setButtonLoading] = useState(false) - const [tooltipStopOpened, setTooltipStopOpened] = useState(false); const [deleteModal, setDeleteModal] = useState(false) const [renameModal, setRenameModal] = useState(false) const [changeDestModal, setChangeDestModal] = useState(false) - const portInputRef = React.createRef() const isMedium = isMediumScreen() const form = useForm({ @@ -113,13 +111,11 @@ export default function ServiceRow({ service }:{ service:Service }) { } onClick={()=>setDeleteModal(true)}>Delete Service - + setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)} - onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}> + aria-describedby="tooltip-stop-id"> diff --git a/frontend/src/components/PyFilterView/index.tsx b/frontend/src/components/PyFilterView/index.tsx index 3602029..f1c647f 100644 --- a/frontend/src/components/PyFilterView/index.tsx +++ b/frontend/src/components/PyFilterView/index.tsx @@ -9,7 +9,6 @@ import { FaPencilAlt } from 'react-icons/fa'; export default function PyFilterView({ filterInfo }:{ filterInfo:PyFilter }) { - const [statusTooltipOpened, setStatusTooltipOpened] = useState(false); const isMedium = isMediumScreen() const changeRegexStatus = () => { @@ -24,24 +23,22 @@ export default function PyFilterView({ filterInfo }:{ filterInfo:PyFilter }) { return - - - {filterInfo.name} - - - {isMedium?<> - {filterInfo.blocked_packets} + + + {filterInfo.name} + - {filterInfo.edited_packets} - - :null} - - setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)} - onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)} - >{filterInfo.active?:} - - + {isMedium?<> + {filterInfo.blocked_packets} + + {filterInfo.edited_packets} + + :null} + + + {filterInfo.active?:} + + } diff --git a/frontend/src/components/RegexView/index.tsx b/frontend/src/components/RegexView/index.tsx index 7f72c68..787ae1b 100644 --- a/frontend/src/components/RegexView/index.tsx +++ b/frontend/src/components/RegexView/index.tsx @@ -19,10 +19,7 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) { let regex_expr = b64decode(regexInfo.regex); const [deleteModal, setDeleteModal] = useState(false); - const [deleteTooltipOpened, setDeleteTooltipOpened] = useState(false); - const [statusTooltipOpened, setStatusTooltipOpened] = useState(false); const clipboard = useClipboard({ timeout: 500 }); - const isMedium = isMediumScreen(); const deleteRegex = () => { nfregex.regexdelete(regexInfo.id).then(res => { @@ -54,18 +51,14 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) { }}>{regex_expr} - + setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)} - onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)} >{regexInfo.active?:} - - setDeleteModal(true)} size="xl" radius="md" variant="filled" - onFocus={() => setDeleteTooltipOpened(false)} onBlur={() => setDeleteTooltipOpened(false)} - onMouseEnter={() => setDeleteTooltipOpened(true)} onMouseLeave={() => setDeleteTooltipOpened(false)} - > + + setDeleteModal(true)} size="xl" radius="md" variant="filled"> + diff --git a/frontend/src/js/utils.tsx b/frontend/src/js/utils.tsx index 39cbdc9..20c7527 100644 --- a/frontend/src/js/utils.tsx +++ b/frontend/src/js/utils.tsx @@ -2,12 +2,11 @@ import { showNotification } from "@mantine/notifications"; import { ImCross } from "react-icons/im"; import { TiTick } from "react-icons/ti" import { Navigate } from "react-router-dom"; -import { nfregex } from "../components/NFRegex/utils"; import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models"; import { Buffer } from "buffer" import { QueryClient, useQuery } from "@tanstack/react-query"; 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 @@ -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 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: { staleTime: Infinity } }}) diff --git a/frontend/src/pages/Firewall/index.tsx b/frontend/src/pages/Firewall/index.tsx index 1517e97..3a26aa1 100644 --- a/frontend/src/pages/Firewall/index.tsx +++ b/frontend/src/pages/Firewall/index.tsx @@ -25,12 +25,7 @@ import { PiWallLight } from "react-icons/pi"; 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.ACCEPT) - const [tooltipAddRulOpened, setTooltipAddRulOpened] = useState(false) const queryClient = useQueryClient() const rules = firewallRulesQuery() const [state, handlers] = useListState([]); @@ -364,31 +359,22 @@ export const Firewall = () => { Rules: {rules.isLoading?0:rules.data?.rules.length} - - setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> + + - + queryClient.invalidateQueries(["firewall"])} size="lg" radius="md" variant="filled" - loading={rules.isFetching} - onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} - onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> + loading={rules.isFetching}> - - setSettingsModal(true)} size="lg" radius="md" variant="filled" - onFocus={() => setTooltipSettingsOpened(false)} onBlur={() => setTooltipSettingsOpened(false)} - onMouseEnter={() => setTooltipSettingsOpened(true)} onMouseLeave={() => setTooltipSettingsOpened(false)}> + + setSettingsModal(true)} size="lg" radius="md" variant="filled"> - - setTooltipApplyOpened(false)} onBlur={() => setTooltipApplyOpened(false)} - onMouseEnter={() => setTooltipApplyOpened(true)} onMouseLeave={() => setTooltipApplyOpened(false)} - disabled={!valuesChanged} - > + + + @@ -424,10 +410,9 @@ export const Firewall = () => { No rule found! Add one clicking the "+" buttons - - setTooltipAddRulOpened(false)} onBlur={() => setTooltipAddRulOpened(false)} - onMouseEnter={() => setTooltipAddRulOpened(true)} onMouseLeave={() => setTooltipAddRulOpened(false)}> + + + } diff --git a/frontend/src/pages/NFProxy/ServiceDetails.tsx b/frontend/src/pages/NFProxy/ServiceDetails.tsx index 855900e..d642541 100644 --- a/frontend/src/pages/NFProxy/ServiceDetails.tsx +++ b/frontend/src/pages/NFProxy/ServiceDetails.tsx @@ -1,12 +1,12 @@ import { ActionIcon, Box, Code, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core'; import { Navigate, useNavigate, useParams } from 'react-router-dom'; 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 { nfproxy, nfproxyServiceFilterCodeQuery, nfproxyServicePyfiltersQuery, nfproxyServiceQuery, serviceQueryKey } from '../../components/NFProxy/utils'; import { MdDoubleArrow } from "react-icons/md" 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 { BiRename } from 'react-icons/bi' import RenameForm from '../../components/NFProxy/ServiceRow/RenameForm'; @@ -19,6 +19,10 @@ import PyFilterView from '../../components/PyFilterView'; import { TbPlugConnected } from 'react-icons/tb'; import { CodeHighlight } from '@mantine/code-highlight'; 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() { @@ -31,11 +35,30 @@ export default function ServiceDetailsNFProxy() { const [editModal, setEditModal] = useState(false) const [buttonLoading, setButtonLoading] = useState(false) const queryClient = useQueryClient() - const [tooltipStopOpened, setTooltipStopOpened] = useState(false); - const [tooltipBackOpened, setTooltipBackOpened] = useState(false); const filterCode = nfproxyServiceFilterCodeQuery(srv??"") const navigate = useNavigate() const isMedium = isMediumScreen() + const [openLogModal, setOpenLogModal] = useState(false) + const [logData, logDataSetters] = useListState([]); + + + 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 if (!srv || !serviceInfo || filtersList.isError) return @@ -101,6 +124,8 @@ export default function ServiceDetailsNFProxy() { {isMedium?null:} + + {serviceInfo.status} @@ -115,7 +140,13 @@ export default function ServiceDetailsNFProxy() { Danger zone } onClick={()=>setDeleteModal(true)}>Delete Service - + + + + setOpenLogModal(true)} loading={buttonLoading} variant="filled"> + + + {isMedium?null:} @@ -133,23 +164,19 @@ export default function ServiceDetailsNFProxy() { {isMedium?null:} - + navigate("/")} size="xl" radius="md" variant="filled" - aria-describedby="tooltip-back-id" - onFocus={() => setTooltipBackOpened(false)} onBlur={() => setTooltipBackOpened(false)} - onMouseEnter={() => setTooltipBackOpened(true)} onMouseLeave={() => setTooltipBackOpened(false)}> + aria-describedby="tooltip-back-id"> - + setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)} - onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}> + aria-describedby="tooltip-stop-id"> @@ -177,7 +204,7 @@ export default function ServiceDetailsNFProxy() { Install the firegex client:<Space w="xs" /><Code mb={-4} >pip install fgex</Code> Then run the command:<Space w="xs" /><Code mb={-4} >fgex nfproxy</Code> - :<>{filtersList.data?.map( (filterInfo) => )} + :<>{filtersList.data?.map( (filterInfo) => )} } setEditModal(false)} edit={serviceInfo} /> + setOpenLogModal(false)} + title={`Logs for service ${serviceInfo.name}`} + data={logData.join("")} + /> } diff --git a/frontend/src/pages/NFProxy/index.tsx b/frontend/src/pages/NFProxy/index.tsx index 0bb4a1f..bdd9edd 100644 --- a/frontend/src/pages/NFProxy/index.tsx +++ b/frontend/src/pages/NFProxy/index.tsx @@ -20,9 +20,6 @@ export default function NFProxy({ children }: { children: any }) { const [open, setOpen] = useState(false); const {srv} = useParams() const queryClient = useQueryClient() - const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); - const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false); - const [tooltipAddOpened, setTooltipAddOpened] = useState(false); const isMedium = isMediumScreen() const services = nfproxyServiceQuery() const fileDialog = useFileDialog({ @@ -116,27 +113,22 @@ export default function NFProxy({ children }: { children: any }) { {isMedium?null:} { srv? - - setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} - onMouseLeave={() => setTooltipAddOpened(false)} onClick={fileDialog.open}> - + + + - : - setOpen(true)} size="lg" radius="md" variant="filled" - onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> + : + setOpen(true)} size="lg" radius="md" variant="filled"> + + } - - queryClient.invalidateQueries(["nfproxy"])} size="lg" radius="md" variant="filled" - loading={services.isFetching} - onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} - onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> + + queryClient.invalidateQueries(["nfproxy"])} size="lg" radius="md" variant="filled" loading={services.isFetching}> + + @@ -148,10 +140,10 @@ export default function NFProxy({ children }: { children: any }) { navigator("/nfproxy/"+srv.service_id) }} />):<> No services found! Add one clicking the "+" buttons - - setOpen(true)} size="xl" radius="md" variant="filled" - onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)} - onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}> + + setOpen(true)} size="xl" radius="md" variant="filled"> + + } diff --git a/frontend/src/pages/NFRegex/ServiceDetails.tsx b/frontend/src/pages/NFRegex/ServiceDetails.tsx index bb6255f..7b3b806 100644 --- a/frontend/src/pages/NFRegex/ServiceDetails.tsx +++ b/frontend/src/pages/NFRegex/ServiceDetails.tsx @@ -27,15 +27,12 @@ export default function ServiceDetailsNFRegex() { const [open, setOpen] = useState(false) const services = nfregexServiceQuery() const serviceInfo = services.data?.find(s => s.service_id == srv) - const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false) const regexesList = nfregexServiceRegexesQuery(srv??"") const [deleteModal, setDeleteModal] = useState(false) const [renameModal, setRenameModal] = useState(false) const [editModal, setEditModal] = useState(false) const [buttonLoading, setButtonLoading] = useState(false) const queryClient = useQueryClient() - const [tooltipStopOpened, setTooltipStopOpened] = useState(false); - const [tooltipBackOpened, setTooltipBackOpened] = useState(false); const navigate = useNavigate() const isMedium = isMediumScreen() @@ -133,23 +130,19 @@ export default function ServiceDetailsNFRegex() { {isMedium?null:} - + navigate("/")} size="xl" radius="md" variant="filled" - aria-describedby="tooltip-back-id" - onFocus={() => setTooltipBackOpened(false)} onBlur={() => setTooltipBackOpened(false)} - onMouseEnter={() => setTooltipBackOpened(true)} onMouseLeave={() => setTooltipBackOpened(false)}> + aria-describedby="tooltip-back-id"> - + setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)} - onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}> + aria-describedby="tooltip-stop-id"> @@ -168,11 +161,9 @@ export default function ServiceDetailsNFRegex() { No regex found for this service! Add one by clicking the "+" buttons - + setOpen(true)} size="xl" radius="md" variant="filled" - aria-describedby="tooltip-AddRegex-id" - onFocus={() => setTooltipAddRegexOpened(false)} onBlur={() => setTooltipAddRegexOpened(false)} - onMouseEnter={() => setTooltipAddRegexOpened(true)} onMouseLeave={() => setTooltipAddRegexOpened(false)}> + aria-describedby="tooltip-AddRegex-id"> : diff --git a/frontend/src/pages/NFRegex/index.tsx b/frontend/src/pages/NFRegex/index.tsx index 83ec802..f5df64d 100644 --- a/frontend/src/pages/NFRegex/index.tsx +++ b/frontend/src/pages/NFRegex/index.tsx @@ -19,9 +19,6 @@ function NFRegex({ children }: { children: any }) { const [open, setOpen] = useState(false); const {srv} = useParams() const queryClient = useQueryClient() - const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); - const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false); - const [tooltipAddOpened, setTooltipAddOpened] = useState(false); const isMedium = isMediumScreen() const services = nfregexServiceQuery() @@ -50,23 +47,17 @@ function NFRegex({ children }: { children: any }) { {isMedium?null:} { srv? - - setOpen(true)} size="lg" radius="md" variant="filled" - onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> + + setOpen(true)} size="lg" radius="md" variant="filled"> - : - setOpen(true)} size="lg" radius="md" variant="filled" - onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> + : + setOpen(true)} size="lg" radius="md" variant="filled"> } - + queryClient.invalidateQueries(["nfregex"])} size="lg" radius="md" variant="filled" - loading={services.isFetching} - onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} - onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> + loading={services.isFetching}> @@ -78,10 +69,8 @@ function NFRegex({ children }: { children: any }) { navigator("/nfregex/"+srv.service_id) }} />):<> No services found! Add one clicking the "+" buttons - - setOpen(true)} size="xl" radius="md" variant="filled" - onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)} - onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}> + + setOpen(true)} size="xl" radius="md" variant="filled"> } diff --git a/frontend/src/pages/PortHijack/index.tsx b/frontend/src/pages/PortHijack/index.tsx index e4f3640..e6fa5ce 100644 --- a/frontend/src/pages/PortHijack/index.tsx +++ b/frontend/src/pages/PortHijack/index.tsx @@ -14,10 +14,7 @@ import { GrDirections } from 'react-icons/gr'; function PortHijack() { const [open, setOpen] = useState(false); - const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false); - const [tooltipAddOpened, setTooltipAddOpened] = useState(false); const queryClient = useQueryClient() - const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); const isMedium = isMediumScreen() const services = porthijackServiceQuery() @@ -37,17 +34,13 @@ function PortHijack() { Services: {services.isLoading?0:services.data?.length} - - setOpen(true)} size="lg" radius="md" variant="filled" - onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> + + setOpen(true)} size="lg" radius="md" variant="filled"> - + queryClient.invalidateQueries(["porthijack"])} size="lg" radius="md" variant="filled" - loading={services.isFetching} - onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} - onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> + loading={services.isFetching}> @@ -57,10 +50,8 @@ function PortHijack() { {(services.data && services.data.length > 0) ?services.data.map( srv => ):<> No services found! Add one clicking the "+" buttons - - setOpen(true)} size="xl" radius="md" variant="filled" - onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)} - onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}> + + setOpen(true)} size="xl" radius="md" variant="filled"> } diff --git a/tests/benchmark.py b/tests/benchmark.py index 02b5aaa..64ace98 100644 --- a/tests/benchmark.py +++ b/tests/benchmark.py @@ -35,7 +35,7 @@ else: def exit_test(code): if service_id: - server.stop() + server.kill() if(firegex.nf_delete_service(service_id)): puts("Sucessfully deleted service ✔", color=colors.green) else: diff --git a/tests/run_tests.sh b/tests/run_tests.sh index cfe00d9..8812f44 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -27,5 +27,10 @@ python3 ph_test.py -p $PASSWORD -m udp || ERROR=1 echo "Running Port Hijack UDP ipv6" 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