nfqueue to hyperscan and stream match, removed proxyregex

This commit is contained in:
Domingo Dirutigliano
2025-02-02 19:54:42 +01:00
parent 3de629ebd5
commit 2d8f19679f
54 changed files with 1134 additions and 3092 deletions

View File

@@ -17,15 +17,9 @@ RUN bun run build
FROM --platform=$TARGETARCH debian:stable-slim AS base
RUN apt-get update -qq && apt-get upgrade -qq && \
apt-get install -qq python3-pip build-essential \
git libpcre2-dev libnetfilter-queue-dev libssl-dev \
libnfnetlink-dev libmnl-dev libcap2-bin make cmake \
nftables libboost-all-dev autoconf automake cargo \
libffi-dev libvectorscan-dev libtins-dev python3-nftables
WORKDIR /tmp/
RUN git clone --single-branch --branch release https://github.com/jpcre2/jpcre2
WORKDIR /tmp/jpcre2
RUN ./configure; make -j`nproc`; make install
git libnetfilter-queue-dev libssl-dev \
libnfnetlink-dev libmnl-dev libcap2-bin \
nftables libffi-dev libvectorscan-dev libtins-dev python3-nftables
RUN mkdir -p /execute/modules
WORKDIR /execute
@@ -34,8 +28,7 @@ ADD ./backend/requirements.txt /execute/requirements.txt
RUN pip3 install --no-cache-dir --break-system-packages -r /execute/requirements.txt --no-warn-script-location
COPY ./backend/binsrc /execute/binsrc
RUN g++ binsrc/nfqueue.cpp -o modules/cppqueue -O3 -lnetfilter_queue -pthread -lpcre2-8 -ltins -lmnl -lnfnetlink
RUN g++ binsrc/proxy.cpp -o modules/proxy -O3 -pthread -lboost_system -lboost_thread -lpcre2-8
RUN g++ binsrc/nfqueue.cpp -o modules/cppqueue -O3 -lnetfilter_queue -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libhs libmnl)
COPY ./backend/ /execute/
COPY --from=frontend /app/dist/ ./frontend/

View File

@@ -1,6 +1,10 @@
import uvicorn, secrets, utils
import os, asyncio, logging
from fastapi import FastAPI, HTTPException, Depends, APIRouter, Request
import uvicorn
import secrets
import utils
import os
import asyncio
import logging
from fastapi import FastAPI, HTTPException, Depends, APIRouter
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import jwt
from passlib.context import CryptContext
@@ -94,7 +98,8 @@ async def get_app_status(auth: bool = Depends(check_login)):
@app.post("/api/login")
async def login_api(form: OAuth2PasswordRequestForm = Depends()):
"""Get a login token to use the firegex api"""
if APP_STATUS() != "run": raise HTTPException(status_code=400)
if APP_STATUS() != "run":
raise HTTPException(status_code=400)
if form.password == "":
return {"status":"Cannot insert an empty password!"}
await asyncio.sleep(0.3) # No bruteforce :)
@@ -105,7 +110,8 @@ async def login_api(form: OAuth2PasswordRequestForm = Depends()):
@app.post('/api/set-password', response_model=ChangePasswordModel)
async def set_password(form: PasswordForm):
"""Set the password of firegex"""
if APP_STATUS() != "init": raise HTTPException(status_code=400)
if APP_STATUS() != "init":
raise HTTPException(status_code=400)
if form.password == "":
return {"status":"Cannot insert an empty password!"}
set_psw(form.password)
@@ -115,7 +121,8 @@ async def set_password(form: PasswordForm):
@api.post('/change-password', response_model=ChangePasswordModel)
async def change_password(form: PasswordChangeForm):
"""Change the password of firegex"""
if APP_STATUS() != "run": raise HTTPException(status_code=400)
if APP_STATUS() != "run":
raise HTTPException(status_code=400)
if form.password == "":
return {"status":"Cannot insert an empty password!"}
@@ -144,7 +151,8 @@ async def startup_main():
except Exception as e:
logging.error(f"Error setting sysctls: {e}")
await startup()
if not JWT_SECRET(): db.put("secret", secrets.token_hex(32))
if not JWT_SECRET():
db.put("secret", secrets.token_hex(32))
await refresh_frontend()
async def shutdown_main():

View File

@@ -0,0 +1,485 @@
#include <linux/netfilter/nfnetlink_queue.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <linux/netfilter/nfnetlink_conntrack.h>
#include <tins/tins.h>
#include <tins/tcp_ip/stream_follower.h>
#include <libmnl/libmnl.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/types.h>
#include <stdexcept>
#include <thread>
#include <hs.h>
#include <iostream>
using Tins::TCPIP::Stream;
using Tins::TCPIP::StreamFollower;
using namespace std;
#ifndef NETFILTER_CLASSES_HPP
#define NETFILTER_CLASSES_HPP
string inline client_endpoint(const Stream& stream) {
ostringstream output;
// Use the IPv4 or IPv6 address depending on which protocol the
// connection uses
if (stream.is_v6()) {
output << stream.client_addr_v6();
}
else {
output << stream.client_addr_v4();
}
output << ":" << stream.client_port();
return output.str();
}
// Convert the server endpoint to a readable string
string inline server_endpoint(const Stream& stream) {
ostringstream output;
if (stream.is_v6()) {
output << stream.server_addr_v6();
}
else {
output << stream.server_addr_v4();
}
output << ":" << stream.server_port();
return output.str();
}
// Concat both endpoints to get a readable stream identifier
string inline stream_identifier(const Stream& stream) {
ostringstream output;
output << client_endpoint(stream) << " - " << server_endpoint(stream);
return output.str();
}
typedef unordered_map<string, hs_stream_t*> matching_map;
struct packet_info;
struct tcp_stream_tmp {
bool matching_has_been_called = false;
bool result;
packet_info *pkt_info;
};
struct stream_ctx {
matching_map in_hs_streams;
matching_map out_hs_streams;
hs_scratch_t* in_scratch = nullptr;
hs_scratch_t* out_scratch = nullptr;
u_int16_t latest_config_ver = 0;
StreamFollower follower;
mnl_socket* nl;
tcp_stream_tmp tcp_match_util;
void clean_scratches(){
if (out_scratch != nullptr){
hs_free_scratch(out_scratch);
out_scratch = nullptr;
}
if (in_scratch != nullptr){
hs_free_scratch(in_scratch);
in_scratch = nullptr;
}
}
};
struct packet_info {
string packet;
string payload;
string stream_id;
bool is_input;
bool is_tcp;
stream_ctx* sctx;
};
typedef bool NetFilterQueueCallback(packet_info &);
Tins::PDU * find_transport_layer(Tins::PDU* pkt){
while(pkt != nullptr){
if (pkt->pdu_type() == Tins::PDU::TCP || pkt->pdu_type() == Tins::PDU::UDP) {
return pkt;
}
pkt = pkt->inner_pdu();
}
return nullptr;
}
template <NetFilterQueueCallback callback_func>
class NetfilterQueue {
public:
size_t BUF_SIZE = 0xffff + (MNL_SOCKET_BUFFER_SIZE/2);
char *buf = nullptr;
unsigned int portid;
u_int16_t queue_num;
stream_ctx sctx;
NetfilterQueue(u_int16_t queue_num): queue_num(queue_num) {
sctx.nl = mnl_socket_open(NETLINK_NETFILTER);
if (sctx.nl == nullptr) { throw runtime_error( "mnl_socket_open" );}
if (mnl_socket_bind(sctx.nl, 0, MNL_SOCKET_AUTOPID) < 0) {
mnl_socket_close(sctx.nl);
throw runtime_error( "mnl_socket_bind" );
}
portid = mnl_socket_get_portid(sctx.nl);
buf = (char*) malloc(BUF_SIZE);
if (!buf) {
mnl_socket_close(sctx.nl);
throw runtime_error( "allocate receive buffer" );
}
if (send_config_cmd(NFQNL_CFG_CMD_BIND) < 0) {
_clear();
throw runtime_error( "mnl_socket_send" );
}
//TEST if BIND was successful
if (send_config_cmd(NFQNL_CFG_CMD_NONE) < 0) { // SEND A NONE cmmand to generate an error meessage
_clear();
throw runtime_error( "mnl_socket_send" );
}
if (recv_packet() == -1) { //RECV the error message
_clear();
throw runtime_error( "mnl_socket_recvfrom" );
}
struct nlmsghdr *nlh = (struct nlmsghdr *) buf;
if (nlh->nlmsg_type != NLMSG_ERROR) {
_clear();
throw runtime_error( "unexpected packet from kernel (expected NLMSG_ERROR packet)" );
}
//nfqnl_msg_config_cmd
nlmsgerr* error_msg = (nlmsgerr *)mnl_nlmsg_get_payload(nlh);
// error code taken from the linux kernel:
// https://elixir.bootlin.com/linux/v5.18.12/source/include/linux/errno.h#L27
#define ENOTSUPP 524 /* Operation is not supported */
if (error_msg->error != -ENOTSUPP) {
_clear();
throw invalid_argument( "queueid is already busy" );
}
//END TESTING BIND
nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num);
nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO));
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO));
if (mnl_socket_sendto(sctx.nl, nlh, nlh->nlmsg_len) < 0) {
_clear();
throw runtime_error( "mnl_socket_send" );
}
}
//Input data filtering
void on_client_data(Stream& stream) {
string data(stream.client_payload().begin(), stream.client_payload().end());
string stream_id = stream_identifier(stream);
this->sctx.tcp_match_util.pkt_info->is_input = true;
this->sctx.tcp_match_util.pkt_info->stream_id = stream_id;
this->sctx.tcp_match_util.matching_has_been_called = true;
bool result = callback_func(*sctx.tcp_match_util.pkt_info);
if (result){
this->clean_stream_by_id(stream_id);
stream.ignore_client_data();
stream.ignore_server_data();
}
this->sctx.tcp_match_util.result = result;
}
//Server data filtering
void on_server_data(Stream& stream) {
string data(stream.server_payload().begin(), stream.server_payload().end());
string stream_id = stream_identifier(stream);
this->sctx.tcp_match_util.pkt_info->is_input = false;
this->sctx.tcp_match_util.pkt_info->stream_id = stream_id;
this->sctx.tcp_match_util.matching_has_been_called = true;
bool result = callback_func(*sctx.tcp_match_util.pkt_info);
if (result){
this->clean_stream_by_id(stream_id);
stream.ignore_client_data();
stream.ignore_server_data();
}
this->sctx.tcp_match_util.result = result;
}
void on_new_stream(Stream& stream) {
string stream_id = stream_identifier(stream);
if (stream.is_partial_stream()) {
return;
}
cout << "[+] New connection " << stream_id << endl;
stream.auto_cleanup_payloads(true);
stream.client_data_callback(
[&](auto a){this->on_client_data(a);}
);
stream.server_data_callback(
[&](auto a){this->on_server_data(a);}
);
}
void clean_stream_by_id(string stream_id){
auto stream_search = this->sctx.in_hs_streams.find(stream_id);
hs_stream_t* stream_match;
if (stream_search != this->sctx.in_hs_streams.end()){
stream_match = stream_search->second;
if (hs_close_stream(stream_match, sctx.in_scratch, nullptr, nullptr) != HS_SUCCESS) {
cerr << "[error] [NetfilterQueue.clean_stream_by_id] Error closing the stream matcher (hs)" << endl;
throw invalid_argument("Cannot close stream match on hyperscan");
}
this->sctx.in_hs_streams.erase(stream_search);
}
stream_search = this->sctx.out_hs_streams.find(stream_id);
if (stream_search != this->sctx.out_hs_streams.end()){
stream_match = stream_search->second;
if (hs_close_stream(stream_match, sctx.out_scratch, nullptr, nullptr) != HS_SUCCESS) {
cerr << "[error] [NetfilterQueue.clean_stream_by_id] Error closing the stream matcher (hs)" << endl;
throw invalid_argument("Cannot close stream match on hyperscan");
}
this->sctx.out_hs_streams.erase(stream_search);
}
}
// A stream was terminated. The second argument is the reason why it was terminated
void on_stream_terminated(Stream& stream, StreamFollower::TerminationReason reason) {
string stream_id = stream_identifier(stream);
cout << "[+] Connection closed: " << stream_id << endl;
this->clean_stream_by_id(stream_id);
}
void run(){
/*
* ENOBUFS is signalled to userspace when packets were lost
* on kernel side. In most cases, userspace isn't interested
* in this information, so turn it off.
*/
int ret = 1;
mnl_socket_setsockopt(sctx.nl, NETLINK_NO_ENOBUFS, &ret, sizeof(int));
sctx.follower.new_stream_callback(
[&](auto a){this->on_new_stream(a);}
);
sctx.follower.stream_termination_callback(
[&](auto a, auto b){this->on_stream_terminated(a, b);}
);
for (;;) {
ret = recv_packet();
if (ret == -1) {
throw runtime_error( "mnl_socket_recvfrom" );
}
ret = mnl_cb_run(buf, ret, 0, portid, queue_cb, &sctx);
if (ret < 0){
throw runtime_error( "mnl_cb_run" );
}
}
}
~NetfilterQueue() {
send_config_cmd(NFQNL_CFG_CMD_UNBIND);
_clear();
}
private:
ssize_t send_config_cmd(nfqnl_msg_config_cmds cmd){
struct nlmsghdr *nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num);
nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, cmd);
return mnl_socket_sendto(sctx.nl, nlh, nlh->nlmsg_len);
}
ssize_t recv_packet(){
return mnl_socket_recvfrom(sctx.nl, buf, BUF_SIZE);
}
void _clear(){
if (buf != nullptr) {
free(buf);
buf = nullptr;
}
mnl_socket_close(sctx.nl);
sctx.nl = nullptr;
sctx.clean_scratches();
for(auto ele: sctx.in_hs_streams){
if (hs_close_stream(ele.second, sctx.in_scratch, nullptr, nullptr) != HS_SUCCESS) {
cerr << "[error] [NetfilterQueue.clean_stream_by_id] Error closing the stream matcher (hs)" << endl;
throw invalid_argument("Cannot close stream match on hyperscan");
}
}
sctx.in_hs_streams.clear();
for(auto ele: sctx.out_hs_streams){
if (hs_close_stream(ele.second, sctx.out_scratch, nullptr, nullptr) != HS_SUCCESS) {
cerr << "[error] [NetfilterQueue.clean_stream_by_id] Error closing the stream matcher (hs)" << endl;
throw invalid_argument("Cannot close stream match on hyperscan");
}
}
sctx.out_hs_streams.clear();
}
static int queue_cb(const nlmsghdr *nlh, void *data_ptr)
{
stream_ctx* sctx = (stream_ctx*)data_ptr;
//Extract attributes from the nlmsghdr
nlattr *attr[NFQA_MAX+1] = {};
if (nfq_nlmsg_parse(nlh, attr) < 0) {
perror("problems parsing");
return MNL_CB_ERROR;
}
if (attr[NFQA_PACKET_HDR] == nullptr) {
fputs("metaheader not set\n", stderr);
return MNL_CB_ERROR;
}
//Get Payload
uint16_t plen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]);
uint8_t *payload = (uint8_t *)mnl_attr_get_payload(attr[NFQA_PAYLOAD]);
//Return result to the kernel
struct nfqnl_msg_packet_hdr *ph = (nfqnl_msg_packet_hdr*) mnl_attr_get_payload(attr[NFQA_PACKET_HDR]);
struct nfgenmsg *nfg = (nfgenmsg *)mnl_nlmsg_get_payload(nlh);
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh_verdict;
struct nlattr *nest;
nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(nfg->res_id));
// Check IP protocol version
Tins::PDU *packet;
if ( ((payload)[0] & 0xf0) == 0x40 ){
Tins::IP parsed = Tins::IP(payload, plen);
packet = &parsed;
}else{
Tins::IPv6 parsed = Tins::IPv6(payload, plen);
packet = &parsed;
}
Tins::PDU *transport_layer = find_transport_layer(packet);
if(transport_layer == nullptr || transport_layer->inner_pdu() == nullptr){
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT );
}else{
bool is_tcp = transport_layer->pdu_type() == Tins::PDU::TCP;
int size = transport_layer->inner_pdu()->size();
packet_info pktinfo{
packet: string(payload, payload+plen),
payload: string(payload+plen - size, payload+plen),
stream_id: "", // TODO We need to calculate this
is_input: true, // TODO We need to detect this
is_tcp: is_tcp,
sctx: sctx,
};
if (is_tcp){
sctx->tcp_match_util.matching_has_been_called = false;
sctx->tcp_match_util.pkt_info = &pktinfo;
sctx->follower.process_packet(*packet);
if (sctx->tcp_match_util.matching_has_been_called && !sctx->tcp_match_util.result){
auto tcp_layer = (Tins::TCP *)transport_layer;
tcp_layer->release_inner_pdu();
tcp_layer->set_flag(Tins::TCP::FIN,1);
tcp_layer->set_flag(Tins::TCP::ACK,1);
tcp_layer->set_flag(Tins::TCP::SYN,0);
nfq_nlmsg_verdict_put_pkt(nlh_verdict, packet->serialize().data(), packet->size());
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT );
delete tcp_layer;
}
}else if(callback_func(pktinfo)){
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT );
} else{
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_DROP );
}
}
/* example to set the connmark. First, start NFQA_CT section: */
nest = mnl_attr_nest_start(nlh_verdict, NFQA_CT);
/* then, add the connmark attribute: */
mnl_attr_put_u32(nlh_verdict, CTA_MARK, htonl(42));
/* more conntrack attributes, e.g. CTA_LABELS could be set here */
/* end conntrack section */
mnl_attr_nest_end(nlh_verdict, nest);
if (mnl_socket_sendto(sctx->nl, nlh_verdict, nlh_verdict->nlmsg_len) < 0) {
throw runtime_error( "mnl_socket_send" );
}
return MNL_CB_OK;
}
};
template <NetFilterQueueCallback func>
class NFQueueSequence{
private:
vector<NetfilterQueue<func> *> nfq;
uint16_t _init;
uint16_t _end;
vector<thread> threads;
public:
static const int QUEUE_BASE_NUM = 1000;
NFQueueSequence(uint16_t seq_len){
if (seq_len <= 0) throw invalid_argument("seq_len <= 0");
nfq = vector<NetfilterQueue<func>*>(seq_len);
_init = QUEUE_BASE_NUM;
while(nfq[0] == nullptr){
if (_init+seq_len-1 >= 65536){
throw runtime_error("NFQueueSequence: too many queues!");
}
for (int i=0;i<seq_len;i++){
try{
nfq[i] = new NetfilterQueue<func>(_init+i);
}catch(const invalid_argument e){
for(int j = 0; j < i; j++) {
delete nfq[j];
nfq[j] = nullptr;
}
_init += seq_len - i;
break;
}
}
}
_end = _init + seq_len - 1;
}
void start(){
if (threads.size() != 0) throw runtime_error("NFQueueSequence: already started!");
for (int i=0;i<nfq.size();i++){
threads.push_back(thread(&NetfilterQueue<func>::run, nfq[i]));
}
}
void join(){
for (int i=0;i<nfq.size();i++){
threads[i].join();
}
threads.clear();
}
uint16_t init(){
return _init;
}
uint16_t end(){
return _end;
}
~NFQueueSequence(){
for (int i=0;i<nfq.size();i++){
delete nfq[i];
}
}
};
#endif // NETFILTER_CLASSES_HPP

View File

@@ -1,294 +0,0 @@
#include <linux/netfilter/nfnetlink_queue.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <linux/netfilter/nfnetlink_conntrack.h>
#include <tins/tins.h>
#include <libmnl/libmnl.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/types.h>
#include <stdexcept>
#include <thread>
#ifndef NETFILTER_CLASSES_HPP
#define NETFILTER_CLASSES_HPP
typedef bool NetFilterQueueCallback(const uint8_t*,uint32_t);
Tins::PDU * find_transport_layer(Tins::PDU* pkt){
while(pkt != NULL){
if (pkt->pdu_type() == Tins::PDU::TCP || pkt->pdu_type() == Tins::PDU::UDP) {
return pkt;
}
pkt = pkt->inner_pdu();
}
return pkt;
}
template <NetFilterQueueCallback callback_func>
class NetfilterQueue {
public:
size_t BUF_SIZE = 0xffff + (MNL_SOCKET_BUFFER_SIZE/2);
char *buf = NULL;
unsigned int portid;
u_int16_t queue_num;
struct mnl_socket* nl = NULL;
NetfilterQueue(u_int16_t queue_num): queue_num(queue_num) {
nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL) { throw std::runtime_error( "mnl_socket_open" );}
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
mnl_socket_close(nl);
throw std::runtime_error( "mnl_socket_bind" );
}
portid = mnl_socket_get_portid(nl);
buf = (char*) malloc(BUF_SIZE);
if (!buf) {
mnl_socket_close(nl);
throw std::runtime_error( "allocate receive buffer" );
}
if (send_config_cmd(NFQNL_CFG_CMD_BIND) < 0) {
_clear();
throw std::runtime_error( "mnl_socket_send" );
}
//TEST if BIND was successful
if (send_config_cmd(NFQNL_CFG_CMD_NONE) < 0) { // SEND A NONE cmmand to generate an error meessage
_clear();
throw std::runtime_error( "mnl_socket_send" );
}
if (recv_packet() == -1) { //RECV the error message
_clear();
throw std::runtime_error( "mnl_socket_recvfrom" );
}
struct nlmsghdr *nlh = (struct nlmsghdr *) buf;
if (nlh->nlmsg_type != NLMSG_ERROR) {
_clear();
throw std::runtime_error( "unexpected packet from kernel (expected NLMSG_ERROR packet)" );
}
//nfqnl_msg_config_cmd
nlmsgerr* error_msg = (nlmsgerr *)mnl_nlmsg_get_payload(nlh);
// error code taken from the linux kernel:
// https://elixir.bootlin.com/linux/v5.18.12/source/include/linux/errno.h#L27
#define ENOTSUPP 524 /* Operation is not supported */
if (error_msg->error != -ENOTSUPP) {
_clear();
throw std::invalid_argument( "queueid is already busy" );
}
//END TESTING BIND
nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num);
nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO));
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO));
if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
_clear();
throw std::runtime_error( "mnl_socket_send" );
}
}
void run(){
/*
* ENOBUFS is signalled to userspace when packets were lost
* on kernel side. In most cases, userspace isn't interested
* in this information, so turn it off.
*/
int ret = 1;
mnl_socket_setsockopt(nl, NETLINK_NO_ENOBUFS, &ret, sizeof(int));
for (;;) {
ret = recv_packet();
if (ret == -1) {
throw std::runtime_error( "mnl_socket_recvfrom" );
}
ret = mnl_cb_run(buf, ret, 0, portid, queue_cb, nl);
if (ret < 0){
throw std::runtime_error( "mnl_cb_run" );
}
}
}
~NetfilterQueue() {
send_config_cmd(NFQNL_CFG_CMD_UNBIND);
_clear();
}
private:
ssize_t send_config_cmd(nfqnl_msg_config_cmds cmd){
struct nlmsghdr *nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num);
nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, cmd);
return mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
}
ssize_t recv_packet(){
return mnl_socket_recvfrom(nl, buf, BUF_SIZE);
}
void _clear(){
if (buf != NULL) {
free(buf);
buf = NULL;
}
mnl_socket_close(nl);
}
static int queue_cb(const struct nlmsghdr *nlh, void *data)
{
struct mnl_socket* nl = (struct mnl_socket*)data;
//Extract attributes from the nlmsghdr
struct nlattr *attr[NFQA_MAX+1] = {};
if (nfq_nlmsg_parse(nlh, attr) < 0) {
perror("problems parsing");
return MNL_CB_ERROR;
}
if (attr[NFQA_PACKET_HDR] == NULL) {
fputs("metaheader not set\n", stderr);
return MNL_CB_ERROR;
}
//Get Payload
uint16_t plen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]);
void *payload = mnl_attr_get_payload(attr[NFQA_PAYLOAD]);
//Return result to the kernel
struct nfqnl_msg_packet_hdr *ph = (nfqnl_msg_packet_hdr*) mnl_attr_get_payload(attr[NFQA_PACKET_HDR]);
struct nfgenmsg *nfg = (nfgenmsg *)mnl_nlmsg_get_payload(nlh);
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh_verdict;
struct nlattr *nest;
nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(nfg->res_id));
/*
This define allow to avoid to allocate new heap memory for each packet.
The code under this comment is replicated for ipv6 and ip
Better solutions are welcome. :)
*/
#define PKT_HANDLE \
Tins::PDU *transport_layer = find_transport_layer(&packet); \
if(transport_layer->inner_pdu() == nullptr || transport_layer == nullptr){ \
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT ); \
}else{ \
int size = transport_layer->inner_pdu()->size(); \
if(callback_func((const uint8_t*)payload+plen - size, size)){ \
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT ); \
} else{ \
if (transport_layer->pdu_type() == Tins::PDU::TCP){ \
((Tins::TCP *)transport_layer)->release_inner_pdu(); \
((Tins::TCP *)transport_layer)->set_flag(Tins::TCP::FIN,1); \
((Tins::TCP *)transport_layer)->set_flag(Tins::TCP::ACK,1); \
((Tins::TCP *)transport_layer)->set_flag(Tins::TCP::SYN,0); \
nfq_nlmsg_verdict_put_pkt(nlh_verdict, packet.serialize().data(), packet.size()); \
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT ); \
}else{ \
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_DROP ); \
} \
} \
}
// Check IP protocol version
if ( (((uint8_t*)payload)[0] & 0xf0) == 0x40 ){
Tins::IP packet = Tins::IP((uint8_t*)payload,plen);
PKT_HANDLE
}else{
Tins::IPv6 packet = Tins::IPv6((uint8_t*)payload,plen);
PKT_HANDLE
}
/* example to set the connmark. First, start NFQA_CT section: */
nest = mnl_attr_nest_start(nlh_verdict, NFQA_CT);
/* then, add the connmark attribute: */
mnl_attr_put_u32(nlh_verdict, CTA_MARK, htonl(42));
/* more conntrack attributes, e.g. CTA_LABELS could be set here */
/* end conntrack section */
mnl_attr_nest_end(nlh_verdict, nest);
if (mnl_socket_sendto(nl, nlh_verdict, nlh_verdict->nlmsg_len) < 0) {
throw std::runtime_error( "mnl_socket_send" );
}
return MNL_CB_OK;
}
};
template <NetFilterQueueCallback func>
class NFQueueSequence{
private:
std::vector<NetfilterQueue<func> *> nfq;
uint16_t _init;
uint16_t _end;
std::vector<std::thread> threads;
public:
static const int QUEUE_BASE_NUM = 1000;
NFQueueSequence(uint16_t seq_len){
if (seq_len <= 0) throw std::invalid_argument("seq_len <= 0");
nfq = std::vector<NetfilterQueue<func>*>(seq_len);
_init = QUEUE_BASE_NUM;
while(nfq[0] == NULL){
if (_init+seq_len-1 >= 65536){
throw std::runtime_error("NFQueueSequence: too many queues!");
}
for (int i=0;i<seq_len;i++){
try{
nfq[i] = new NetfilterQueue<func>(_init+i);
}catch(const std::invalid_argument e){
for(int j = 0; j < i; j++) {
delete nfq[j];
nfq[j] = nullptr;
}
_init += seq_len - i;
break;
}
}
}
_end = _init + seq_len - 1;
}
void start(){
if (threads.size() != 0) throw std::runtime_error("NFQueueSequence: already started!");
for (int i=0;i<nfq.size();i++){
threads.push_back(std::thread(&NetfilterQueue<func>::run, nfq[i]));
}
}
void join(){
for (int i=0;i<nfq.size();i++){
threads[i].join();
}
threads.clear();
}
uint16_t init(){
return _init;
}
uint16_t end(){
return _end;
}
~NFQueueSequence(){
for (int i=0;i<nfq.size();i++){
delete nfq[i];
}
}
};
#endif // NETFILTER_CLASSES_HPP

View File

@@ -1,95 +0,0 @@
#include <iostream>
#include <cstring>
#include <jpcre2.hpp>
#include <sstream>
#include "../utils.hpp"
#ifndef REGEX_FILTER_HPP
#define REGEX_FILTER_HPP
typedef jpcre2::select<char> jp;
typedef std::pair<std::string,jp::Regex> regex_rule_pair;
typedef std::vector<regex_rule_pair> regex_rule_vector;
struct regex_rules{
regex_rule_vector output_whitelist, input_whitelist, output_blacklist, input_blacklist;
regex_rule_vector* getByCode(char code){
switch(code){
case 'C': // Client to server Blacklist
return &input_blacklist; break;
case 'c': // Client to server Whitelist
return &input_whitelist; break;
case 'S': // Server to client Blacklist
return &output_blacklist; break;
case 's': // Server to client Whitelist
return &output_whitelist; break;
}
throw std::invalid_argument( "Expected 'C' 'c' 'S' or 's'" );
}
int add(const char* arg){
//Integrity checks
size_t arg_len = strlen(arg);
if (arg_len < 2 || arg_len%2 != 0){
std::cerr << "[warning] [regex_rules.add] invalid arg passed (" << arg << "), skipping..." << std::endl;
return -1;
}
if (arg[0] != '0' && arg[0] != '1'){
std::cerr << "[warning] [regex_rules.add] invalid is_case_sensitive (" << arg[0] << ") in '" << arg << "', must be '1' or '0', skipping..." << std::endl;
return -1;
}
if (arg[1] != 'C' && arg[1] != 'c' && arg[1] != 'S' && arg[1] != 's'){
std::cerr << "[warning] [regex_rules.add] invalid filter_type (" << arg[1] << ") in '" << arg << "', must be 'C', 'c', 'S' or 's', skipping..." << std::endl;
return -1;
}
std::string hex(arg+2), expr;
if (!unhexlify(hex, expr)){
std::cerr << "[warning] [regex_rules.add] invalid hex regex value (" << hex << "), skipping..." << std::endl;
return -1;
}
//Push regex
jp::Regex regex(expr,arg[0] == '1'?"gS":"giS");
if (regex){
std::cerr << "[info] [regex_rules.add] adding new regex filter: '" << expr << "'" << std::endl;
getByCode(arg[1])->push_back(std::make_pair(std::string(arg), regex));
} else {
std::cerr << "[warning] [regex_rules.add] compiling of '" << expr << "' regex failed, skipping..." << std::endl;
return -1;
}
return 0;
}
bool check(unsigned char* data, const size_t& bytes_transferred, const bool in_input){
std::string str_data((char *) data, bytes_transferred);
for (regex_rule_pair ele:(in_input?input_blacklist:output_blacklist)){
try{
if(ele.second.match(str_data)){
std::stringstream msg;
msg << "BLOCKED " << ele.first << "\n";
std::cout << msg.str() << std::flush;
return false;
}
} catch(...){
std::cerr << "[info] [regex_rules.check] Error while matching blacklist regex: " << ele.first << std::endl;
}
}
for (regex_rule_pair ele:(in_input?input_whitelist:output_whitelist)){
try{
std::cerr << "[debug] [regex_rules.check] regex whitelist match " << ele.second.getPattern() << std::endl;
if(!ele.second.match(str_data)){
std::stringstream msg;
msg << "BLOCKED " << ele.first << "\n";
std::cout << msg.str() << std::flush;
return false;
}
} catch(...){
std::cerr << "[info] [regex_rules.check] Error while matching whitelist regex: " << ele.first << std::endl;
}
}
return true;
}
};
#endif // REGEX_FILTER_HPP

View File

@@ -0,0 +1,161 @@
#include <iostream>
#include <cstring>
#include <sstream>
#include "../utils.hpp"
#include <vector>
#include <hs.h>
using namespace std;
#ifndef REGEX_FILTER_HPP
#define REGEX_FILTER_HPP
enum FilterDirection{ CTOS, STOC };
struct decoded_regex {
string regex;
FilterDirection direction;
bool is_case_sensitive;
};
struct regex_ruleset {
hs_database_t* hs_db;
char** regexes;
};
decoded_regex decode_regex(string regex){
size_t arg_len = regex.size();
if (arg_len < 2 || arg_len%2 != 0){
cerr << "[warning] [decode_regex] invalid arg passed (" << regex << "), skipping..." << endl;
throw runtime_error( "Invalid expression len (too small)" );
}
if (regex[0] != '0' && regex[0] != '1'){
cerr << "[warning] [decode_regex] invalid is_case_sensitive (" << regex[0] << ") in '" << regex << "', must be '1' or '0', skipping..." << endl;
throw runtime_error( "Invalid is_case_sensitive" );
}
if (regex[1] != 'C' && regex[1] != 'S'){
cerr << "[warning] [decode_regex] invalid filter_direction (" << regex[1] << ") in '" << regex << "', must be 'C', 'S', skipping..." << endl;
throw runtime_error( "Invalid filter_direction" );
}
string hex(regex.c_str()+2), expr;
if (!unhexlify(hex, expr)){
cerr << "[warning] [decode_regex] invalid hex regex value (" << hex << "), skipping..." << endl;
throw runtime_error( "Invalid hex regex encoded value" );
}
decoded_regex ruleset{
regex: expr,
direction: regex[1] == 'C'? CTOS : STOC,
is_case_sensitive: regex[0] == '1'
};
return ruleset;
}
class RegexRules{
public:
regex_ruleset output_ruleset, input_ruleset;
private:
static inline u_int16_t glob_seq = 0;
u_int16_t version;
vector<pair<string, decoded_regex>> decoded_input_rules;
vector<pair<string, decoded_regex>> decoded_output_rules;
bool is_stream = true;
void free_dbs(){
if (output_ruleset.hs_db != nullptr){
hs_free_database(output_ruleset.hs_db);
}
if (input_ruleset.hs_db != nullptr){
hs_free_database(input_ruleset.hs_db);
}
}
void fill_ruleset(vector<pair<string, decoded_regex>> & decoded, regex_ruleset & ruleset){
size_t n_of_regex = decoded.size();
if (n_of_regex == 0){
return;
}
const char* regex_match_rules[n_of_regex];
unsigned int regex_array_ids[n_of_regex];
unsigned int regex_flags[n_of_regex];
for(int i = 0; i < n_of_regex; i++){
regex_match_rules[i] = decoded[i].second.regex.c_str();
regex_array_ids[i] = i;
regex_flags[i] = HS_FLAG_SINGLEMATCH | HS_FLAG_ALLOWEMPTY;
if (!decoded[i].second.is_case_sensitive){
regex_flags[i] |= HS_FLAG_CASELESS;
}
}
hs_database_t* rebuilt_db;
hs_compile_error_t *compile_err;
if (
hs_compile_multi(
regex_match_rules,
regex_flags,
regex_array_ids,
n_of_regex,
is_stream?HS_MODE_STREAM:HS_MODE_BLOCK,
nullptr,&rebuilt_db, &compile_err
) != HS_SUCCESS
) {
cerr << "[warning] [RegexRules.fill_ruleset] hs_db failed to compile: '" << compile_err->message << "' skipping..." << endl;
hs_free_compile_error(compile_err);
throw runtime_error( "Failed to compile hyperscan db" );
}
ruleset.hs_db = rebuilt_db;
}
public:
RegexRules(vector<string> raw_rules, bool is_stream){
this->is_stream = is_stream;
this->version = ++glob_seq; // 0 version is a invalid version (useful for some logics)
for(string ele : raw_rules){
try{
decoded_regex rule = decode_regex(ele);
if (rule.direction == FilterDirection::CTOS){
decoded_input_rules.push_back(make_pair(ele, rule));
}else{
decoded_output_rules.push_back(make_pair(ele, rule));
}
}catch(...){
throw current_exception();
}
}
fill_ruleset(decoded_input_rules, input_ruleset);
try{
fill_ruleset(decoded_output_rules, output_ruleset);
}catch(...){
free_dbs();
throw current_exception();
}
}
u_int16_t ver(){
return version;
}
RegexRules(bool is_stream){
vector<string> no_rules;
RegexRules(no_rules, is_stream);
}
bool stream_mode(){
return is_stream;
}
RegexRules(){
RegexRules(true);
}
~RegexRules(){
free_dbs();
}
};
#endif // REGEX_FILTER_HPP

BIN
backend/binsrc/cppqueue Executable file

Binary file not shown.

View File

@@ -1,11 +1,11 @@
#include "classes/regex_filter.hpp"
#include "classes/netfilter.hpp"
#include "classes/regex_rules.cpp"
#include "classes/netfilter.cpp"
#include "utils.hpp"
#include <iostream>
using namespace std;
shared_ptr<regex_rules> regex_config;
shared_ptr<RegexRules> regex_config;
void config_updater (){
string line;
@@ -21,44 +21,116 @@ void config_updater (){
}
cerr << "[info] [updater] Updating configuration with line " << line << endl;
istringstream config_stream(line);
regex_rules *regex_new_config = new regex_rules();
vector<string> raw_rules;
while(!config_stream.eof()){
string data;
config_stream >> data;
if (data != "" && data != "\n"){
regex_new_config->add(data.c_str());
raw_rules.push_back(data);
}
}
regex_config.reset(regex_new_config);
try{
regex_config.reset(new RegexRules(raw_rules, regex_config->stream_mode()));
cerr << "[info] [updater] Config update done" << endl;
}catch(...){
cerr << "[error] [updater] Failed to build new configuration!" << endl;
// TODO send a row on stdout for this error
}
}
}
template <bool is_input>
bool filter_callback(const uint8_t *data, uint32_t len){
shared_ptr<regex_rules> current_config = regex_config;
return current_config->check((unsigned char *)data, len, is_input);
void inline scratch_setup(regex_ruleset &conf, hs_scratch_t* & scratch){
if (scratch == nullptr){
if (hs_alloc_scratch(conf.hs_db, &scratch) != HS_SUCCESS) {
throw invalid_argument("Cannot alloc scratch");
}
}
}
int main(int argc, char *argv[])
{
struct matched_data{
unsigned int matched = 0;
bool has_matched = false;
};
bool filter_callback(packet_info & info){
shared_ptr<RegexRules> conf = regex_config;
if (conf->ver() != info.sctx->latest_config_ver){
info.sctx->clean_scratches();
}
scratch_setup(conf->input_ruleset, info.sctx->in_scratch);
scratch_setup(conf->output_ruleset, info.sctx->out_scratch);
hs_database_t* regex_matcher = info.is_input ? conf->input_ruleset.hs_db : conf->output_ruleset.hs_db;
if (regex_matcher == nullptr){
return true;
}
matched_data match_res;
hs_error_t err;
hs_scratch_t* scratch_space = info.is_input ? info.sctx->in_scratch: info.sctx->out_scratch;
auto match_func = [](unsigned int id, auto from, auto to, auto flags, auto ctx){
auto res = (matched_data*)ctx;
res->has_matched = true;
res->matched = id;
return 1; // Stop matching
};
if (conf->stream_mode()){
matching_map match_map = info.is_input ? info.sctx->in_hs_streams : info.sctx->out_hs_streams;
auto stream_search = match_map.find(info.stream_id);
hs_stream_t* stream_match;
if (stream_search == match_map.end()){
if (hs_open_stream(regex_matcher, 0, &stream_match) != HS_SUCCESS) {
cerr << "[error] [filter_callback] Error opening the stream matcher (hs)" << endl;
throw invalid_argument("Cannot open stream match on hyperscan");
}
match_map[info.stream_id] = stream_match;
}else{
stream_match = stream_search->second;
}
err = hs_scan_stream(
stream_match,info.payload.c_str(), info.payload.length(),
0, scratch_space, match_func, &match_res
);
}else{
err = hs_scan(
regex_matcher,info.payload.c_str(), info.payload.length(),
0, scratch_space, match_func, &match_res
);
}
if (err != HS_SUCCESS) {
cerr << "[error] [filter_callback] Error while matching the stream (hs)" << endl;
throw invalid_argument("Error while matching the stream with hyperscan");
}
if (match_res.has_matched){
auto rules_vector = info.is_input ? conf->input_ruleset.regexes : conf->output_ruleset.regexes;
stringstream msg;
msg << "BLOCKED " << rules_vector[match_res.matched] << "\n";
cout << msg.str() << flush;
return false;
}
return true;
}
int main(int argc, char *argv[]){
int n_of_threads = 1;
char * n_threads_str = getenv("NTHREADS");
if (n_threads_str != NULL) n_of_threads = ::atoi(n_threads_str);
if (n_threads_str != nullptr) n_of_threads = ::atoi(n_threads_str);
if(n_of_threads <= 0) n_of_threads = 1;
if (n_of_threads % 2 != 0 ) n_of_threads++;
cerr << "[info] [main] Using " << n_of_threads << " threads" << endl;
regex_config.reset(new regex_rules());
NFQueueSequence<filter_callback<true>> input_queues(n_of_threads/2);
input_queues.start();
NFQueueSequence<filter_callback<false>> output_queues(n_of_threads/2);
output_queues.start();
cout << "QUEUES INPUT " << input_queues.init() << " " << input_queues.end() << " OUTPUT " << output_queues.init() << " " << output_queues.end() << endl;
cerr << "[info] [main] Input queues: " << input_queues.init() << ":" << input_queues.end() << " threads assigned: " << n_of_threads/2 << endl;
cerr << "[info] [main] Output queues: " << output_queues.init() << ":" << output_queues.end() << " threads assigned: " << n_of_threads/2 << endl;
char * matchmode = getenv("MATCH_MODE");
bool stream_mode = true;
if (matchmode != nullptr && strcmp(matchmode, "block") == 0){
stream_mode = false;
}
cerr << "[info] [main] Using " << n_of_threads << " threads" << endl;
regex_config.reset(new RegexRules(stream_mode));
NFQueueSequence<filter_callback> queues(n_of_threads);
queues.start();
cout << "QUEUES " << queues.init() << " " << queues.end() << endl;
cerr << "[info] [main] Queues: " << queues.init() << ":" << queues.end() << " threads assigned: " << n_of_threads << endl;
config_updater();
}

View File

@@ -1,32 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "atomic_refcell"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "nfq"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9c8f4c88952507d9df9400a6a2e48640fb460e21dcb2b4716eb3ff156d6db9e"
dependencies = [
"libc",
]
[[package]]
name = "nfqueue_regex"
version = "0.1.0"
dependencies = [
"atomic_refcell",
"nfq",
]

View File

@@ -1,11 +0,0 @@
[package]
name = "nfqueue_regex"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
atomic_refcell = "0.1.13"
nfq = "0.2.5"
#hyperscan = "0.3.2"

View File

@@ -1,150 +0,0 @@
use atomic_refcell::AtomicRefCell;
use nfq::{Queue, Verdict};
use std::cell::{Cell, RefCell};
use std::env;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::atomic::{AtomicPtr, AtomicU32};
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::Arc;
use std::thread::{self, sleep, sleep_ms, JoinHandle};
enum WorkerMessage {
Error(String),
Dropped(usize),
}
impl ToString for WorkerMessage {
fn to_string(&self) -> String {
match self {
WorkerMessage::Error(e) => format!("E{}", e),
WorkerMessage::Dropped(d) => format!("D{}", d),
}
}
}
struct Pool {
_workers: Vec<Worker>,
pub start: u16,
pub end: u16,
}
const QUEUE_BASE_NUM: u16 = 1000;
impl Pool {
fn new(threads: u16, tx: Sender<WorkerMessage>, db: RefCell<&str>) -> Self {
// Find free queues
let mut start = QUEUE_BASE_NUM;
let mut queues: Vec<(Queue, u16)> = vec![];
while queues.len() != threads.into() {
for queue_num in
(start..start.checked_add(threads + 1).expect("No more queues left")).rev()
{
let mut queue = Queue::open().unwrap();
if queue.bind(queue_num).is_err() {
start = queue_num;
while let Some((mut q, num)) = queues.pop() {
let _ = q.unbind(num);
}
break;
};
queues.push((queue, queue_num));
}
}
Pool {
_workers: queues
.into_iter()
.map(|(queue, queue_num)| Worker::new(queue, queue_num, tx.clone()))
.collect(),
start,
end: (start + threads),
}
}
// fn join(self) {
// for worker in self._workers {
// let _ = worker.join();
// }
// }
}
struct Worker {
_inner: JoinHandle<()>,
}
impl Worker {
fn new(mut queue: Queue, _queue_num: u16, tx: Sender<WorkerMessage>) -> Self {
Worker {
_inner: thread::spawn(move || loop {
let mut msg = queue.recv().unwrap_or_else(|_| {
let _ = tx.send(WorkerMessage::Error("Fuck".to_string()));
panic!("");
});
msg.set_verdict(Verdict::Accept);
queue.verdict(msg).unwrap();
}),
}
}
}
struct InputOuputPools {
pub output_queue: Pool,
pub input_queue: Pool,
rx: Receiver<WorkerMessage>,
}
impl InputOuputPools {
fn new(threads: u16) -> InputOuputPools {
let (tx, rx) = mpsc::channel();
InputOuputPools {
output_queue: Pool::new(threads / 2, tx.clone(), RefCell::new("ciao")),
input_queue: Pool::new(threads / 2, tx, RefCell::new("miao")),
rx,
}
}
fn poll_events(&self) {
loop {
let event = self.rx.recv().expect("Channel has hung up");
println!("{}", event.to_string());
}
}
}
static mut DB: AtomicPtr<Arc<u32>> = AtomicPtr::new(std::ptr::null_mut() as *mut Arc<u32>);
fn main() -> std::io::Result<()> {
let mut my_x: Arc<u32> = Arc::new(0);
let my_x_ptr: *mut Arc<u32> = std::ptr::addr_of_mut!(my_x);
unsafe { DB.store(my_x_ptr, std::sync::atomic::Ordering::SeqCst) };
thread::spawn(|| loop {
let x_ptr = unsafe { DB.load(std::sync::atomic::Ordering::SeqCst) };
let x = unsafe { (*x_ptr).clone() };
dbg!(x);
//sleep_ms(1000);
});
for i in 0..1000000000 {
let mut my_x: Arc<u32> = Arc::new(i);
let my_x_ptr: *mut Arc<u32> = std::ptr::addr_of_mut!(my_x);
unsafe { DB.store(my_x_ptr, std::sync::atomic::Ordering::SeqCst) };
//sleep_ms(100);
}
let mut threads = env::var("NPROCS").unwrap_or_default().parse().unwrap_or(2);
if threads % 2 != 0 {
threads += 1;
}
let in_out_pools = InputOuputPools::new(threads);
eprintln!(
"[info] [main] Input queues: {}:{}",
in_out_pools.input_queue.start, in_out_pools.input_queue.end
);
eprintln!(
"[info] [main] Output queues: {}:{}",
in_out_pools.output_queue.start, in_out_pools.output_queue.end
);
in_out_pools.poll_events();
Ok(())
}

View File

@@ -1,493 +0,0 @@
/*
Copyright (c) 2007 Arash Partow (http://www.partow.net)
URL: http://www.partow.net/programming/tcpproxy/index.html
Modified and adapted by Pwnzer0tt1
*/
#include <cstdlib>
#include <cstddef>
#include <iostream>
#include <string>
#include <mutex>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread/mutex.hpp>
#include <jpcre2.hpp>
typedef jpcre2::select<char> jp;
using namespace std;
bool unhexlify(string const &hex, string &newString) {
try{
int len = hex.length();
for(int i=0; i< len; i+=2)
{
std::string byte = hex.substr(i,2);
char chr = (char) (int)strtol(byte.c_str(), NULL, 16);
newString.push_back(chr);
}
return true;
}
catch (...){
return false;
}
}
typedef pair<string,jp::Regex> regex_rule_pair;
typedef vector<regex_rule_pair> regex_rule_vector;
struct regex_rules{
regex_rule_vector regex_s_c_w, regex_c_s_w, regex_s_c_b, regex_c_s_b;
regex_rule_vector* getByCode(char code){
switch(code){
case 'C': // Client to server Blacklist
return &regex_c_s_b; break;
case 'c': // Client to server Whitelist
return &regex_c_s_w; break;
case 'S': // Server to client Blacklist
return &regex_s_c_b; break;
case 's': // Server to client Whitelist
return &regex_s_c_w; break;
}
throw invalid_argument( "Expected 'C' 'c' 'S' or 's'" );
}
void add(const char* arg){
//Integrity checks
size_t arg_len = strlen(arg);
if (arg_len < 2 || arg_len%2 != 0) return;
if (arg[0] != '0' && arg[0] != '1') return;
if (arg[1] != 'C' && arg[1] != 'c' && arg[1] != 'S' && arg[1] != 's') return;
string hex(arg+2), expr;
if (!unhexlify(hex, expr)) return;
//Push regex
jp::Regex regex(expr,arg[0] == '1'?"gS":"giS");
if (regex){
#ifdef DEBUG
cerr << "Added regex " << expr << " " << arg << endl;
#endif
getByCode(arg[1])->push_back(make_pair(string(arg), regex));
} else {
cerr << "Regex " << arg << " was not compiled successfully" << endl;
}
}
};
shared_ptr<regex_rules> regex_config;
mutex update_mutex;
bool filter_data(unsigned char* data, const size_t& bytes_transferred, regex_rule_vector const &blacklist, regex_rule_vector const &whitelist){
#ifdef DEBUG_PACKET
cerr << "---------------- Packet ----------------" << endl;
for(int i=0;i<bytes_transferred;i++) cerr << data[i];
cerr << endl;
for(int i=0;i<bytes_transferred;i++) fprintf(stderr, "%x", data[i]);
cerr << endl;
cerr << "---------------- End Packet ----------------" << endl;
#endif
string str_data((char *) data, bytes_transferred);
for (regex_rule_pair ele:blacklist){
try{
if(ele.second.match(str_data)){
stringstream msg;
msg << "BLOCKED " << ele.first << endl;
cout << msg.str() << std::flush;
return false;
}
} catch(...){
cerr << "Error while matching regex: " << ele.first << endl;
}
}
for (regex_rule_pair ele:whitelist){
try{
if(!ele.second.match(str_data)){
stringstream msg;
msg << "BLOCKED " << ele.first << endl;
cout << msg.str() << std::flush;
return false;
}
} catch(...){
cerr << "Error while matching regex: " << ele.first << endl;
}
}
#ifdef DEBUG
cerr << "Packet Accepted!" << endl;
#endif
return true;
}
namespace tcp_proxy
{
namespace ip = boost::asio::ip;
class bridge : public boost::enable_shared_from_this<bridge>
{
public:
typedef ip::tcp::socket socket_type;
typedef boost::shared_ptr<bridge> ptr_type;
bridge(boost::asio::io_context& ios)
: downstream_socket_(ios),
upstream_socket_ (ios),
thread_safety(ios)
{}
socket_type& downstream_socket()
{
// Client socket
return downstream_socket_;
}
socket_type& upstream_socket()
{
// Remote server socket
return upstream_socket_;
}
void start(const string& upstream_host, unsigned short upstream_port)
{
// Attempt connection to remote server (upstream side)
upstream_socket_.async_connect(
ip::tcp::endpoint(
boost::asio::ip::address::from_string(upstream_host),
upstream_port),
boost::asio::bind_executor(thread_safety,
boost::bind(
&bridge::handle_upstream_connect,
shared_from_this(),
boost::asio::placeholders::error)));
}
void handle_upstream_connect(const boost::system::error_code& error)
{
if (!error)
{
// Setup async read from remote server (upstream)
upstream_socket_.async_read_some(
boost::asio::buffer(upstream_data_,max_data_length),
boost::asio::bind_executor(thread_safety,
boost::bind(&bridge::handle_upstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
// Setup async read from client (downstream)
downstream_socket_.async_read_some(
boost::asio::buffer(downstream_data_,max_data_length),
boost::asio::bind_executor(thread_safety,
boost::bind(&bridge::handle_downstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
else
close();
}
private:
/*
Section A: Remote Server --> Proxy --> Client
Process data recieved from remote sever then send to client.
*/
// Read from remote server complete, now send data to client
void handle_upstream_read(const boost::system::error_code& error,
const size_t& bytes_transferred) // Da Server a Client
{
if (!error)
{
shared_ptr<regex_rules> regex_old_config = regex_config;
if (filter_data(upstream_data_, bytes_transferred, regex_old_config->regex_s_c_b, regex_old_config->regex_s_c_w)){
async_write(downstream_socket_,
boost::asio::buffer(upstream_data_,bytes_transferred),
boost::asio::bind_executor(thread_safety,
boost::bind(&bridge::handle_downstream_write,
shared_from_this(),
boost::asio::placeholders::error)));
}else{
close();
}
}
else
close();
}
// Write to client complete, Async read from remote server
void handle_downstream_write(const boost::system::error_code& error)
{
if (!error)
{
upstream_socket_.async_read_some(
boost::asio::buffer(upstream_data_,max_data_length),
boost::asio::bind_executor(thread_safety,
boost::bind(&bridge::handle_upstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
else
close();
}
// *** End Of Section A ***
/*
Section B: Client --> Proxy --> Remove Server
Process data recieved from client then write to remove server.
*/
// Read from client complete, now send data to remote server
void handle_downstream_read(const boost::system::error_code& error,
const size_t& bytes_transferred) // Da Client a Server
{
if (!error)
{
shared_ptr<regex_rules> regex_old_config = regex_config;
if (filter_data(downstream_data_, bytes_transferred, regex_old_config->regex_c_s_b, regex_old_config->regex_c_s_w)){
async_write(upstream_socket_,
boost::asio::buffer(downstream_data_,bytes_transferred),
boost::asio::bind_executor(thread_safety,
boost::bind(&bridge::handle_upstream_write,
shared_from_this(),
boost::asio::placeholders::error)));
}else{
close();
}
}
else
close();
}
// Write to remote server complete, Async read from client
void handle_upstream_write(const boost::system::error_code& error)
{
if (!error)
{
downstream_socket_.async_read_some(
boost::asio::buffer(downstream_data_,max_data_length),
boost::asio::bind_executor(thread_safety,
boost::bind(&bridge::handle_downstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
else
close();
}
// *** End Of Section B ***
void close()
{
boost::mutex::scoped_lock lock(mutex_);
if (downstream_socket_.is_open())
{
downstream_socket_.close();
}
if (upstream_socket_.is_open())
{
upstream_socket_.close();
}
}
socket_type downstream_socket_;
socket_type upstream_socket_;
enum { max_data_length = 8192 }; //8KB
unsigned char downstream_data_[max_data_length];
unsigned char upstream_data_ [max_data_length];
boost::asio::io_context::strand thread_safety;
boost::mutex mutex_;
public:
class acceptor
{
public:
acceptor(boost::asio::io_context& io_context,
const string& local_host, unsigned short local_port,
const string& upstream_host, unsigned short upstream_port)
: io_context_(io_context),
localhost_address(boost::asio::ip::address_v4::from_string(local_host)),
acceptor_(io_context_,ip::tcp::endpoint(localhost_address,local_port)),
upstream_port_(upstream_port),
upstream_host_(upstream_host)
{}
bool accept_connections()
{
try
{
session_ = boost::shared_ptr<bridge>(new bridge(io_context_));
acceptor_.async_accept(session_->downstream_socket(),
boost::asio::bind_executor(session_->thread_safety,
boost::bind(&acceptor::handle_accept,
this,
boost::asio::placeholders::error)));
}
catch(exception& e)
{
cerr << "acceptor exception: " << e.what() << endl;
return false;
}
return true;
}
private:
void handle_accept(const boost::system::error_code& error)
{
if (!error)
{
session_->start(upstream_host_,upstream_port_);
if (!accept_connections())
{
cerr << "Failure during call to accept." << endl;
}
}
else
{
cerr << "Error: " << error.message() << endl;
}
}
boost::asio::io_context& io_context_;
ip::address_v4 localhost_address;
ip::tcp::acceptor acceptor_;
ptr_type session_;
unsigned short upstream_port_;
string upstream_host_;
};
};
}
void update_config (boost::asio::streambuf &input_buffer){
#ifdef DEBUG
cerr << "Updating configuration" << endl;
#endif
std::istream config_stream(&input_buffer);
std::unique_lock<std::mutex> lck(update_mutex);
regex_rules *regex_new_config = new regex_rules();
string data;
while(true){
config_stream >> data;
if (config_stream.eof()) break;
regex_new_config->add(data.c_str());
}
regex_config.reset(regex_new_config);
}
class async_updater
{
public:
async_updater(boost::asio::io_context& io_context) : input_(io_context, ::dup(STDIN_FILENO)), thread_safety(io_context)
{
boost::asio::async_read_until(input_, input_buffer_, '\n',
boost::asio::bind_executor(thread_safety,
boost::bind(&async_updater::on_update, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void on_update(const boost::system::error_code& error, std::size_t length)
{
if (!error)
{
update_config(input_buffer_);
boost::asio::async_read_until(input_, input_buffer_, '\n',
boost::asio::bind_executor(thread_safety,
boost::bind(&async_updater::on_update, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
else
{
close();
}
}
void close()
{
input_.close();
}
private:
boost::asio::posix::stream_descriptor input_;
boost::asio::io_context::strand thread_safety;
boost::asio::streambuf input_buffer_;
};
int main(int argc, char* argv[])
{
if (argc < 5)
{
cerr << "usage: tcpproxy_server <local host ip> <local port> <forward host ip> <forward port>" << endl;
return 1;
}
const unsigned short local_port = static_cast<unsigned short>(::atoi(argv[2]));
const unsigned short forward_port = static_cast<unsigned short>(::atoi(argv[4]));
const string local_host = argv[1];
const string forward_host = argv[3];
int threads = 1;
char * n_threads_str = getenv("NTHREADS");
if (n_threads_str != NULL) threads = ::atoi(n_threads_str);
boost::asio::io_context ios;
boost::asio::streambuf buf;
boost::asio::posix::stream_descriptor cin_in(ios, ::dup(STDIN_FILENO));
boost::asio::read_until(cin_in, buf,'\n');
update_config(buf);
async_updater updater(ios);
#ifdef DEBUG
cerr << "Starting Proxy" << endl;
#endif
try
{
tcp_proxy::bridge::acceptor acceptor(ios,
local_host, local_port,
forward_host, forward_port);
acceptor.accept_connections();
if (threads > 1){
boost::thread_group tg;
for (unsigned i = 0; i < threads; ++i)
tg.create_thread(boost::bind(&boost::asio::io_context::run, &ios));
tg.join_all();
}else{
ios.run();
}
}
catch(exception& e)
{
cerr << "Error: " << e.what() << endl;
return 1;
}
#ifdef DEBUG
cerr << "Proxy stopped!" << endl;
#endif
return 0;
}

View File

@@ -10,7 +10,7 @@ bool unhexlify(std::string const &hex, std::string &newString) {
for(int i=0; i< len; i+=2)
{
std::string byte = hex.substr(i,2);
char chr = (char) (int)strtol(byte.c_str(), NULL, 16);
char chr = (char) (int)strtol(byte.c_str(), nullptr, 16);
newString.push_back(chr);
}
return true;

View File

@@ -1,6 +1,6 @@
import asyncio
from modules.firewall.nftables import FiregexTables
from modules.firewall.models import *
from modules.firewall.models import Rule, FirewallSettings
from utils.sqlite import SQLite
from modules.firewall.models import Action
@@ -131,5 +131,5 @@ class FirewallManager:
return self.db.get("allow_dhcp", "1") == "1"
@drop_invalid.setter
def allow_dhcp(self, value):
def allow_dhcp_set(self, value):
self.db.set("allow_dhcp", "1" if value else "0")

View File

@@ -1,7 +1,9 @@
from modules.nfregex.nftables import FiregexTables
from utils import ip_parse, run_func
from utils import run_func
from modules.nfregex.models import Service, Regex
import re, os, asyncio
import re
import os
import asyncio
import traceback
nft = FiregexTables()
@@ -20,7 +22,8 @@ class RegexFilter:
self.regex = regex
self.is_case_sensitive = is_case_sensitive
self.is_blacklist = is_blacklist
if input_mode == output_mode: input_mode = output_mode = True # (False, False) == (True, True)
if input_mode == output_mode:
input_mode = output_mode = True # (False, False) == (True, True)
self.input_mode = input_mode
self.output_mode = output_mode
self.blocked = blocked_packets
@@ -37,8 +40,10 @@ class RegexFilter:
update_func = update_func
)
def compile(self):
if isinstance(self.regex, str): self.regex = self.regex.encode()
if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether")
if isinstance(self.regex, str):
self.regex = self.regex.encode()
if not isinstance(self.regex, bytes):
raise Exception("Invalid Regex Paramether")
re.compile(self.regex) # raise re.error if it's invalid!
case_sensitive = "1" if self.is_case_sensitive else "0"
if self.input_mode:
@@ -67,9 +72,9 @@ class FiregexInterceptor:
self.srv = srv
self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock()
input_range, output_range = await self._start_binary()
queue_range = await self._start_binary()
self.update_task = asyncio.create_task(self.update_blocked())
nft.add(self.srv, input_range, output_range)
nft.add(self.srv, queue_range)
return self
async def _start_binary(self):
@@ -87,7 +92,7 @@ class FiregexInterceptor:
line = line_fut.decode()
if line.startswith("QUEUES "):
params = line.split()
return (int(params[2]), int(params[3])), (int(params[5]), int(params[6]))
return (int(params[1]), int(params[2]))
else:
self.process.kill()
raise Exception("Invalid binary output")
@@ -102,8 +107,10 @@ class FiregexInterceptor:
if regex_id in self.filter_map:
self.filter_map[regex_id].blocked+=1
await self.filter_map[regex_id].update()
except asyncio.CancelledError: pass
except asyncio.IncompleteReadError: pass
except asyncio.CancelledError:
pass
except asyncio.IncompleteReadError:
pass
except Exception:
traceback.print_exc()
@@ -135,6 +142,7 @@ class FiregexInterceptor:
raw_filters = filter_obj.compile()
for filter in raw_filters:
res[filter] = filter_obj
except Exception: pass
except Exception:
pass
return res

View File

@@ -30,14 +30,15 @@ class ServiceManager:
new_filters = set([f.id for f in regexes])
#remove old filters
for f in old_filters:
if not f in new_filters:
if f not in new_filters:
del self.filters[f]
#add new filters
for f in new_filters:
if not f in old_filters:
if f not in old_filters:
filter = [ele for ele in regexes if ele.id == f][0]
self.filters[f] = RegexFilter.from_regex(filter, self._stats_updater)
if self.interceptor: await self.interceptor.reload(self.filters.values())
if self.interceptor:
await self.interceptor.reload(self.filters.values())
def __update_status_db(self, status):
self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.srv.id)
@@ -114,4 +115,5 @@ class FirewallManager:
else:
raise ServiceNotFoundException()
class ServiceNotFoundException(Exception): pass
class ServiceNotFoundException(Exception):
pass

View File

@@ -45,14 +45,15 @@ class FiregexTables(NFTableManager):
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
])
def add(self, srv:Service, queue_range_input, queue_range_output):
def add(self, srv:Service, queue_range):
for ele in self.get():
if ele.__eq__(srv): return
init, end = queue_range_output
init, end = queue_range
if init > end: init, end = end, init
self.cmd({ "insert":{ "rule": {
self.cmd(
{ "insert":{ "rule": {
"family": "inet",
"table": self.table_name,
"chain": self.output_chain,
@@ -61,11 +62,8 @@ class FiregexTables(NFTableManager):
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.port)}},
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
]
}}})
init, end = queue_range_input
if init > end: init, end = end, init
self.cmd({"insert":{"rule":{
}}},
{"insert":{"rule":{
"family": "inet",
"table": self.table_name,
"chain": self.input_chain,
@@ -74,7 +72,8 @@ class FiregexTables(NFTableManager):
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "dport"}}, "op": "==", "right": int(srv.port)}},
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
]
}}})
}}}
)
def get(self) -> list[FiregexFilter]:

View File

@@ -5,7 +5,8 @@ from utils.sqlite import SQLite
nft = FiregexTables()
class ServiceNotFoundException(Exception): pass
class ServiceNotFoundException(Exception):
pass
class ServiceManager:
def __init__(self, srv: Service, db):
@@ -29,7 +30,8 @@ class ServiceManager:
async def refresh(self, srv:Service):
self.srv = srv
if self.active: await self.restart()
if self.active:
await self.restart()
def _set_status(self,active):
self.active = active

View File

@@ -50,7 +50,8 @@ class FiregexTables(NFTableManager):
def add(self, srv:Service):
for ele in self.get():
if ele.__eq__(srv): return
if ele.__eq__(srv):
return
self.cmd({ "insert":{ "rule": {
"family": "inet",

View File

@@ -1,116 +0,0 @@
import re, os, asyncio
class Filter:
def __init__(self, regex, is_case_sensitive=True, is_blacklist=True, c_to_s=False, s_to_c=False, blocked_packets=0, code=None):
self.regex = regex
self.is_case_sensitive = is_case_sensitive
self.is_blacklist = is_blacklist
if c_to_s == s_to_c: c_to_s = s_to_c = True # (False, False) == (True, True)
self.c_to_s = c_to_s
self.s_to_c = s_to_c
self.blocked = blocked_packets
self.code = code
def compile(self):
if isinstance(self.regex, str): self.regex = self.regex.encode()
if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether")
re.compile(self.regex) # raise re.error if is invalid!
case_sensitive = "1" if self.is_case_sensitive else "0"
if self.c_to_s:
yield case_sensitive + "C" + self.regex.hex() if self.is_blacklist else case_sensitive + "c"+ self.regex.hex()
if self.s_to_c:
yield case_sensitive + "S" + self.regex.hex() if self.is_blacklist else case_sensitive + "s"+ self.regex.hex()
class Proxy:
def __init__(self, internal_port=0, public_port=0, callback_blocked_update=None, filters=None, public_host="0.0.0.0", internal_host="127.0.0.1"):
self.filter_map = {}
self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock()
self.status_change = asyncio.Lock()
self.public_host = public_host
self.public_port = public_port
self.internal_host = internal_host
self.internal_port = internal_port
self.filters = set(filters) if filters else set([])
self.process = None
self.callback_blocked_update = callback_blocked_update
async def start(self, in_pause=False):
await self.status_change.acquire()
if not self.isactive():
try:
self.filter_map = self.compile_filters()
filters_codes = self.get_filter_codes() if not in_pause else []
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../proxy")
self.process = await asyncio.create_subprocess_exec(
proxy_binary_path, str(self.public_host), str(self.public_port), str(self.internal_host), str(self.internal_port),
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
)
await self.update_config(filters_codes)
finally:
self.status_change.release()
try:
while True:
buff = await self.process.stdout.readuntil()
stdout_line = buff.decode()
if stdout_line.startswith("BLOCKED"):
regex_id = stdout_line.split()[1]
async with self.filter_map_lock:
if regex_id in self.filter_map:
self.filter_map[regex_id].blocked+=1
if self.callback_blocked_update: self.callback_blocked_update(self.filter_map[regex_id])
except Exception:
return await self.process.wait()
else:
self.status_change.release()
async def stop(self):
async with self.status_change:
if self.isactive():
self.process.kill()
return False
return True
async def restart(self, in_pause=False):
status = await self.stop()
await self.start(in_pause=in_pause)
return status
async def update_config(self, filters_codes):
async with self.update_config_lock:
if (self.isactive()):
self.process.stdin.write((" ".join(filters_codes)+"\n").encode())
await self.process.stdin.drain()
async def reload(self):
if self.isactive():
async with self.filter_map_lock:
self.filter_map = self.compile_filters()
filters_codes = self.get_filter_codes()
await self.update_config(filters_codes)
def get_filter_codes(self):
filters_codes = list(self.filter_map.keys())
filters_codes.sort(key=lambda a: self.filter_map[a].blocked, reverse=True)
return filters_codes
def isactive(self):
return self.process and self.process.returncode is None
async def pause(self):
if self.isactive():
await self.update_config([])
else:
await self.start(in_pause=True)
def compile_filters(self):
res = {}
for filter_obj in self.filters:
try:
raw_filters = filter_obj.compile()
for filter in raw_filters:
res[filter] = filter_obj
except Exception: pass
return res

View File

@@ -1,199 +0,0 @@
import secrets
from modules.regexproxy.proxy import Filter, Proxy
import random, socket, asyncio
from base64 import b64decode
from utils.sqlite import SQLite
from utils import socketio_emit
class STATUS:
WAIT = "wait"
STOP = "stop"
PAUSE = "pause"
ACTIVE = "active"
class ServiceNotFoundException(Exception): pass
class ServiceManager:
def __init__(self, id, db):
self.id = id
self.db = db
self.proxy = Proxy(
internal_host="127.0.0.1",
callback_blocked_update=self._stats_updater
)
self.status = STATUS.STOP
self.wanted_status = STATUS.STOP
self.filters = {}
self._update_port_from_db()
self._update_filters_from_db()
self.lock = asyncio.Lock()
self.starter = None
def _update_port_from_db(self):
res = self.db.query("""
SELECT
public_port,
internal_port
FROM services WHERE service_id = ?;
""", self.id)
if len(res) == 0: raise ServiceNotFoundException()
self.proxy.internal_port = res[0]["internal_port"]
self.proxy.public_port = res[0]["public_port"]
def _update_filters_from_db(self):
res = self.db.query("""
SELECT
regex, mode, regex_id `id`, is_blacklist,
blocked_packets n_packets, is_case_sensitive
FROM regexes WHERE service_id = ? AND active=1;
""", self.id)
#Filter check
old_filters = set(self.filters.keys())
new_filters = set([f["id"] for f in res])
#remove old filters
for f in old_filters:
if not f in new_filters:
del self.filters[f]
for f in new_filters:
if not f in old_filters:
filter_info = [ele for ele in res if ele["id"] == f][0]
self.filters[f] = Filter(
is_case_sensitive=filter_info["is_case_sensitive"],
c_to_s=filter_info["mode"] in ["C","B"],
s_to_c=filter_info["mode"] in ["S","B"],
is_blacklist=filter_info["is_blacklist"],
regex=b64decode(filter_info["regex"]),
blocked_packets=filter_info["n_packets"],
code=f
)
self.proxy.filters = list(self.filters.values())
def __update_status_db(self, status):
self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.id)
async def next(self,to):
async with self.lock:
return await self._next(to)
async def _next(self, to):
if self.status != to:
# ACTIVE -> PAUSE or PAUSE -> ACTIVE
if (self.status, to) in [(STATUS.ACTIVE, STATUS.PAUSE)]:
await self.proxy.pause()
self._set_status(to)
elif (self.status, to) in [(STATUS.PAUSE, STATUS.ACTIVE)]:
await self.proxy.reload()
self._set_status(to)
# ACTIVE -> STOP
elif (self.status,to) in [(STATUS.ACTIVE, STATUS.STOP), (STATUS.WAIT, STATUS.STOP), (STATUS.PAUSE, STATUS.STOP)]: #Stop proxy
if self.starter: self.starter.cancel()
await self.proxy.stop()
self._set_status(to)
# STOP -> ACTIVE or STOP -> PAUSE
elif (self.status, to) in [(STATUS.STOP, STATUS.ACTIVE), (STATUS.STOP, STATUS.PAUSE)]:
self.wanted_status = to
self._set_status(STATUS.WAIT)
self.__proxy_starter(to)
def _stats_updater(self,filter:Filter):
self.db.query("UPDATE regexes SET blocked_packets = ? WHERE regex_id = ?;", filter.blocked, filter.code)
async def update_port(self):
async with self.lock:
self._update_port_from_db()
if self.status in [STATUS.PAUSE, STATUS.ACTIVE]:
next_status = self.status if self.status != STATUS.WAIT else self.wanted_status
await self._next(STATUS.STOP)
await self._next(next_status)
def _set_status(self,status):
self.status = status
self.__update_status_db(status)
async def update_filters(self):
async with self.lock:
self._update_filters_from_db()
if self.status in [STATUS.PAUSE, STATUS.ACTIVE]:
await self.proxy.reload()
def __proxy_starter(self,to):
async def func():
try:
while True:
if check_port_is_open(self.proxy.public_port):
self._set_status(to)
await socketio_emit(["regexproxy"])
await self.proxy.start(in_pause=(to==STATUS.PAUSE))
self._set_status(STATUS.STOP)
await socketio_emit(["regexproxy"])
return
else:
await asyncio.sleep(.5)
except asyncio.CancelledError:
self._set_status(STATUS.STOP)
await self.proxy.stop()
self.starter = asyncio.create_task(func())
class ProxyManager:
def __init__(self, db:SQLite):
self.db = db
self.proxy_table: dict[str, ServiceManager] = {}
self.lock = asyncio.Lock()
async def close(self):
for key in list(self.proxy_table.keys()):
await self.remove(key)
async def remove(self,id):
async with self.lock:
if id in self.proxy_table:
await self.proxy_table[id].next(STATUS.STOP)
del self.proxy_table[id]
async def reload(self):
async with self.lock:
for srv in self.db.query('SELECT service_id, status FROM services;'):
srv_id, req_status = srv["service_id"], srv["status"]
if srv_id in self.proxy_table:
continue
self.proxy_table[srv_id] = ServiceManager(srv_id,self.db)
await self.proxy_table[srv_id].next(req_status)
def get(self,id) -> ServiceManager:
if id in self.proxy_table:
return self.proxy_table[id]
else:
raise ServiceNotFoundException()
def check_port_is_open(port):
try:
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0',port))
sock.close()
return True
except Exception:
return False
def gen_service_id(db):
while True:
res = secrets.token_hex(8)
if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0:
break
return res
def gen_internal_port(db):
while True:
res = random.randint(30000, 45000)
if len(db.query('SELECT 1 FROM services WHERE internal_port = ?;', res)) == 0:
break
return res

View File

@@ -5,7 +5,7 @@ from utils import ip_parse, ip_family, socketio_emit
from utils.models import ResetRequest, StatusMessageModel
from modules.firewall.nftables import FiregexTables
from modules.firewall.firewall import FirewallManager
from modules.firewall.models import *
from modules.firewall.models import FirewallSettings, RuleInfo, RuleModel, RuleFormAdd, Mode, Table
db = SQLite('db/firewall-rules.db', {
'rules': {

View File

@@ -147,7 +147,8 @@ async def get_service_by_id(service_id: str):
FROM services s LEFT JOIN regexes r ON s.service_id = r.service_id
WHERE s.service_id = ? GROUP BY s.service_id;
""", service_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
if len(res) == 0:
raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
@@ -177,7 +178,8 @@ async def service_delete(service_id: str):
async def service_rename(service_id: str, form: RenameForm):
"""Request to change the name of a specific service"""
form.name = refactor_name(form.name)
if not form.name: raise HTTPException(status_code=400, detail="The name cannot be empty!")
if not form.name:
raise HTTPException(status_code=400, detail="The name cannot be empty!")
try:
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
except sqlite3.IntegrityError:
@@ -188,7 +190,8 @@ async def service_rename(service_id: str, form: RenameForm):
@app.get('/service/{service_id}/regexes', response_model=list[RegexModel])
async def get_service_regexe_list(service_id: str):
"""Get the list of the regexes of a service"""
if not db.query("SELECT 1 FROM services s WHERE s.service_id = ?;", service_id): raise HTTPException(status_code=400, detail="This service does not exists!")
if not db.query("SELECT 1 FROM services s WHERE s.service_id = ?;", service_id):
raise HTTPException(status_code=400, detail="This service does not exists!")
return db.query("""
SELECT
regex, mode, regex_id `id`, service_id, is_blacklist,
@@ -205,7 +208,8 @@ async def get_regex_by_id(regex_id: int):
blocked_packets n_packets, is_case_sensitive, active
FROM regexes WHERE `id` = ?;
""", regex_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This regex does not exists!")
if len(res) == 0:
raise HTTPException(status_code=400, detail="This regex does not exists!")
return res[0]
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)

View File

@@ -96,7 +96,8 @@ async def get_service_list():
async def get_service_by_id(service_id: str):
"""Get info about a specific service using his id"""
res = db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services WHERE service_id = ?;", service_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
if len(res) == 0:
raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
@@ -125,7 +126,8 @@ async def service_delete(service_id: str):
async def service_rename(service_id: str, form: RenameForm):
"""Request to change the name of a specific service"""
form.name = refactor_name(form.name)
if not form.name: raise HTTPException(status_code=400, detail="The name cannot be empty!")
if not form.name:
raise HTTPException(status_code=400, detail="The name cannot be empty!")
try:
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
except sqlite3.IntegrityError:

View File

@@ -1,311 +0,0 @@
from base64 import b64decode
import sqlite3, re
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from modules.regexproxy.utils import STATUS, ProxyManager, gen_internal_port, gen_service_id
from utils.sqlite import SQLite
from utils.models import ResetRequest, StatusMessageModel
from utils import refactor_name, socketio_emit, PortType
app = APIRouter()
db = SQLite("db/regextcpproxy.db",{
'services': {
'status': 'VARCHAR(100) NOT NULL',
'service_id': 'VARCHAR(100) PRIMARY KEY',
'internal_port': 'INT NOT NULL CHECK(internal_port > 0 and internal_port < 65536)',
'public_port': 'INT NOT NULL CHECK(internal_port > 0 and internal_port < 65536) UNIQUE',
'name': 'VARCHAR(100) NOT NULL UNIQUE'
},
'regexes': {
'regex': 'TEXT NOT NULL',
'mode': 'VARCHAR(1) NOT NULL',
'service_id': 'VARCHAR(100) NOT NULL',
'is_blacklist': 'BOOLEAN NOT NULL CHECK (is_blacklist IN (0, 1))',
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
'regex_id': 'INTEGER PRIMARY KEY',
'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))',
'active' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1)) DEFAULT 1',
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)',
},
'QUERY':[
"CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);"
]
})
firewall = ProxyManager(db)
async def reset(params: ResetRequest):
if not params.delete:
db.backup()
await firewall.close()
if params.delete:
db.delete()
db.init()
else:
db.restore()
await firewall.reload()
async def startup():
db.init()
await firewall.reload()
async def shutdown():
db.backup()
await firewall.close()
db.disconnect()
db.restore()
async def refresh_frontend(additional:list[str]=[]):
await socketio_emit(["regexproxy"]+additional)
class GeneralStatModel(BaseModel):
closed:int
regexes: int
services: int
@app.get('/stats', response_model=GeneralStatModel)
async def get_general_stats():
"""Get firegex general status about services"""
return db.query("""
SELECT
(SELECT COALESCE(SUM(blocked_packets),0) FROM regexes) closed,
(SELECT COUNT(*) FROM regexes) regexes,
(SELECT COUNT(*) FROM services) services
""")[0]
class ServiceModel(BaseModel):
id:str
status: str
public_port: PortType
internal_port: PortType
name: str
n_regex: int
n_packets: int
@app.get('/services', response_model=list[ServiceModel])
async def get_service_list():
"""Get the list of existent firegex services"""
return db.query("""
SELECT
s.service_id `id`,
s.status status,
s.public_port public_port,
s.internal_port internal_port,
s.name name,
COUNT(r.regex_id) n_regex,
COALESCE(SUM(r.blocked_packets),0) n_packets
FROM services s LEFT JOIN regexes r ON r.service_id = s.service_id
GROUP BY s.service_id;
""")
@app.get('/service/{service_id}', response_model=ServiceModel)
async def get_service_by_id(service_id: str):
"""Get info about a specific service using his id"""
res = db.query("""
SELECT
s.service_id `id`,
s.status status,
s.public_port public_port,
s.internal_port internal_port,
s.name name,
COUNT(r.regex_id) n_regex,
COALESCE(SUM(r.blocked_packets),0) n_packets
FROM services s LEFT JOIN regexes r ON r.service_id = s.service_id WHERE s.service_id = ?
GROUP BY s.service_id;
""", service_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
async def service_stop(service_id: str):
"""Request the stop of a specific service"""
await firewall.get(service_id).next(STATUS.STOP)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/pause', response_model=StatusMessageModel)
async def service_pause(service_id: str):
"""Request the pause of a specific service"""
await firewall.get(service_id).next(STATUS.PAUSE)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
async def service_start(service_id: str):
"""Request the start of a specific service"""
await firewall.get(service_id).next(STATUS.ACTIVE)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
async def service_delete(service_id: str):
"""Request the deletion of a specific service"""
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
await firewall.remove(service_id)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/regen-port', response_model=StatusMessageModel)
async def regen_service_port(service_id: str):
"""Request the regeneration of a the internal proxy port of a specific service"""
db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', gen_internal_port(db), service_id)
await firewall.get(service_id).update_port()
await refresh_frontend()
return {'status': 'ok'}
class ChangePortForm(BaseModel):
port: int|None = None
internalPort: int|None = None
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel)
async def change_service_ports(service_id: str, change_port:ChangePortForm):
"""Choose and change the ports of the service"""
if change_port.port is None and change_port.internalPort is None:
raise HTTPException(status_code=400, detail="Invalid Request!")
try:
sql_inj = ""
query:list[str|int] = []
if not change_port.port is None:
sql_inj+=" public_port = ? "
query.append(change_port.port)
if not change_port.port is None and not change_port.internalPort is None:
sql_inj += ","
if not change_port.internalPort is None:
sql_inj+=" internal_port = ? "
query.append(change_port.internalPort)
query.append(service_id)
db.query(f'UPDATE services SET {sql_inj} WHERE service_id = ?;', *query)
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="Port of the service has been already assigned to another service")
await firewall.get(service_id).update_port()
await refresh_frontend()
return {'status': 'ok'}
class RegexModel(BaseModel):
regex:str
mode:str
id:int
service_id:str
is_blacklist: bool
n_packets:int
is_case_sensitive:bool
active:bool
@app.get('/service/{service_id}/regexes', response_model=list[RegexModel])
async def get_service_regexe_list(service_id: str):
"""Get the list of the regexes of a service"""
if not db.query("SELECT 1 FROM services s WHERE s.service_id = ?;", service_id): raise HTTPException(status_code=400, detail="This service does not exists!")
return db.query("""
SELECT
regex, mode, regex_id `id`, service_id, is_blacklist,
blocked_packets n_packets, is_case_sensitive, active
FROM regexes WHERE service_id = ?;
""", service_id)
@app.get('/regex/{regex_id}', response_model=RegexModel)
async def get_regex_by_id(regex_id: int):
"""Get regex info using his id"""
res = db.query("""
SELECT
regex, mode, regex_id `id`, service_id, is_blacklist,
blocked_packets n_packets, is_case_sensitive, active
FROM regexes WHERE `id` = ?;
""", regex_id)
if len(res) == 0: raise HTTPException(status_code=400, detail="This regex does not exists!")
return res[0]
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
async def regex_delete(regex_id: int):
"""Delete a regex using his id"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
db.query('DELETE FROM regexes WHERE regex_id = ?;', regex_id)
await firewall.get(res[0]["service_id"]).update_filters()
await refresh_frontend()
return {'status': 'ok'}
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
async def regex_enable(regex_id: int):
"""Request the enabling of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
db.query('UPDATE regexes SET active=1 WHERE regex_id = ?;', regex_id)
await firewall.get(res[0]["service_id"]).update_filters()
await refresh_frontend()
return {'status': 'ok'}
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
async def regex_disable(regex_id: int):
"""Request the deactivation of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
db.query('UPDATE regexes SET active=0 WHERE regex_id = ?;', regex_id)
await firewall.get(res[0]["service_id"]).update_filters()
await refresh_frontend()
return {'status': 'ok'}
class RegexAddForm(BaseModel):
service_id: str
regex: str
mode: str
active: bool|None = None
is_blacklist: bool
is_case_sensitive: bool
@app.post('/regexes/add', response_model=StatusMessageModel)
async def add_new_regex(form: RegexAddForm):
"""Add a new regex"""
try:
re.compile(b64decode(form.regex))
except Exception:
raise HTTPException(status_code=400, detail="Invalid regex")
try:
db.query("INSERT INTO regexes (service_id, regex, is_blacklist, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?, ?);",
form.service_id, form.regex, form.is_blacklist, form.mode, form.is_case_sensitive, True if form.active is None else form.active )
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="An identical regex already exists")
await firewall.get(form.service_id).update_filters()
await refresh_frontend()
return {'status': 'ok'}
class ServiceAddForm(BaseModel):
name: str
port: PortType
internalPort: int|None = None
class ServiceAddStatus(BaseModel):
status:str
id: str|None = None
class RenameForm(BaseModel):
name:str
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
async def service_rename(service_id: str, form: RenameForm):
"""Request to change the name of a specific service"""
form.name = refactor_name(form.name)
if not form.name: raise HTTPException(status_code=400, detail="The name cannot be empty!")
try:
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="The name is already used!")
await refresh_frontend()
return {'status': 'ok'}
@app.post('/services/add', response_model=ServiceAddStatus)
async def add_new_service(form: ServiceAddForm):
"""Add a new service"""
serv_id = gen_service_id(db)
form.name = refactor_name(form.name)
try:
internal_port = form.internalPort if form.internalPort else gen_internal_port(db)
db.query("INSERT INTO services (name, service_id, internal_port, public_port, status) VALUES (?, ?, ?, ?, ?)",
form.name, serv_id, internal_port, form.port, 'stop')
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="Name or/and ports of the service has been already assigned to another service")
await firewall.reload()
await refresh_frontend()
return {'status': 'ok', "id": serv_id }

View File

@@ -1,10 +1,13 @@
import asyncio
from ipaddress import ip_address, ip_interface
import os, socket, psutil, sys, nftables
import os
import socket
import psutil
import sys
import nftables
from fastapi_socketio import SocketManager
from fastapi import Path
from typing import Annotated
import json
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
@@ -31,7 +34,8 @@ async def socketio_emit(elements:list[str]):
def refactor_name(name:str):
name = name.strip()
while " " in name: name = name.replace(" "," ")
while " " in name:
name = name.replace(" "," ")
return name
class SysctlManager:
@@ -125,8 +129,10 @@ class NFTableManager(Singleton):
def cmd(self, *cmds):
code, out, err = self.raw_cmd(*cmds)
if code == 0: return out
else: raise Exception(err)
if code == 0:
return out
else:
raise Exception(err)
def init(self):
self.reset()
@@ -138,8 +144,10 @@ class NFTableManager(Singleton):
def list_rules(self, tables = None, chains = None):
for filter in [ele["rule"] for ele in self.raw_list() if "rule" in ele ]:
if tables and filter["table"] not in tables: continue
if chains and filter["chain"] not in chains: continue
if tables and filter["table"] not in tables:
continue
if chains and filter["chain"] not in chains:
continue
yield filter
def raw_list(self):

View File

@@ -1,5 +1,6 @@
import os, httpx
import os
import httpx
from typing import Callable
from fastapi import APIRouter
from starlette.responses import StreamingResponse
@@ -31,7 +32,8 @@ def frontend_deploy(app):
return await frontend_debug_proxy(full_path)
except Exception:
return {"details":"Frontend not started at "+f"http://127.0.0.1:{os.getenv('F_PORT','5173')}"}
else: return await react_deploy(full_path)
else:
return await react_deploy(full_path)
def list_routers():
return [ele[:-3] for ele in list_files(ROUTERS_DIR) if ele != "__init__.py" and " " not in ele and ele.endswith(".py")]
@@ -79,9 +81,12 @@ def load_routers(app):
if router.shutdown:
shutdowns.append(router.shutdown)
async def reset(reset_option:ResetRequest):
for func in resets: await run_func(func, reset_option)
for func in resets:
await run_func(func, reset_option)
async def startup():
for func in startups: await run_func(func)
for func in startups:
await run_func(func)
async def shutdown():
for func in shutdowns: await run_func(func)
for func in shutdowns:
await run_func(func)
return reset, startup, shutdown

View File

@@ -1,4 +1,6 @@
import json, sqlite3, os
import json
import sqlite3
import os
from hashlib import md5
class SQLite():
@@ -15,8 +17,10 @@ class SQLite():
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
except Exception:
path_name = os.path.dirname(self.db_name)
if not os.path.exists(path_name): os.makedirs(path_name)
with open(self.db_name, 'x'): pass
if not os.path.exists(path_name):
os.makedirs(path_name)
with open(self.db_name, 'x'):
pass
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
def dict_factory(cursor, row):
d = {}
@@ -36,13 +40,15 @@ class SQLite():
with open(self.db_name, "wb") as f:
f.write(self.__backup)
self.__backup = None
if were_active: self.connect()
if were_active:
self.connect()
def delete_backup(self):
self.__backup = None
def disconnect(self) -> None:
if self.conn: self.conn.close()
if self.conn:
self.conn.close()
self.conn = None
def create_schema(self, tables = {}) -> None:
@@ -50,9 +56,11 @@ class SQLite():
cur = self.conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS main.keys_values(key VARCHAR(100) PRIMARY KEY, value VARCHAR(100) NOT NULL);")
for t in tables:
if t == "QUERY": continue
if t == "QUERY":
continue
cur.execute('CREATE TABLE IF NOT EXISTS main.{}({});'.format(t, ''.join([(c + ' ' + tables[t][c] + ', ') for c in tables[t]])[:-2]))
if "QUERY" in tables: [cur.execute(qry) for qry in tables["QUERY"]]
if "QUERY" in tables:
[cur.execute(qry) for qry in tables["QUERY"]]
cur.close()
def query(self, query, *values):
@@ -82,8 +90,10 @@ class SQLite():
raise e
finally:
cur.close()
try: self.conn.commit()
except Exception: pass
try:
self.conn.commit()
except Exception:
pass
def delete(self):
self.disconnect()
@@ -92,7 +102,8 @@ class SQLite():
def init(self):
self.connect()
try:
if self.get('DB_VERSION') != self.DB_VER: raise Exception("DB_VERSION is not correct")
if self.get('DB_VERSION') != self.DB_VER:
raise Exception("DB_VERSION is not correct")
except Exception:
self.delete()
self.connect()

Binary file not shown.

View File

@@ -5,25 +5,25 @@
"private": true,
"dependencies": {
"@hello-pangea/dnd": "^16.6.0",
"@mantine/core": "^7.13.2",
"@mantine/form": "^7.13.2",
"@mantine/hooks": "^7.13.2",
"@mantine/modals": "^7.13.2",
"@mantine/notifications": "^7.13.2",
"@mantine/core": "^7.16.2",
"@mantine/form": "^7.16.2",
"@mantine/hooks": "^7.16.2",
"@mantine/modals": "^7.16.2",
"@mantine/notifications": "^7.16.2",
"@tanstack/react-query": "^4.36.1",
"@types/jest": "^27.5.2",
"@types/node": "^20.16.11",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/node": "^20.17.16",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"buffer": "^6.0.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.27.0",
"socket.io-client": "^4.8.0",
"react-icons": "^5.4.0",
"react-router-dom": "^6.29.0",
"socket.io-client": "^4.8.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4",
"zustand": "^5.0.0-rc.2"
"zustand": "^5.0.3"
},
"scripts": {
"dev": "vite",
@@ -50,8 +50,8 @@
},
"devDependencies": {
"@tanstack/react-query-devtools": "^4.36.1",
"@vitejs/plugin-react": "^4.3.2",
"vite": "^4.5.5",
"@vitejs/plugin-react": "^4.3.4",
"vite": "^4.5.9",
"vite-plugin-svgr": "^3.3.0",
"vite-tsconfig-paths": "^4.3.2"
}

View File

@@ -8,9 +8,7 @@ import { PasswordSend, ServerStatusResponse } from './js/models';
import { DEV_IP_BACKEND, errorNotify, getstatus, HomeRedirector, IS_DEV, login, setpassword } from './js/utils';
import NFRegex from './pages/NFRegex';
import io from 'socket.io-client';
import RegexProxy from './pages/RegexProxy';
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
import PortHijack from './pages/PortHijack';
import { Firewall } from './pages/Firewall';
import { useQueryClient } from '@tanstack/react-query';
@@ -150,9 +148,6 @@ function App() {
<Route path="nfregex" element={<NFRegex><Outlet /></NFRegex>} >
<Route path=":srv" element={<ServiceDetailsNFRegex />} />
</Route>
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
<Route path=":srv" element={<ServiceDetailsProxyRegex />} />
</Route>
<Route path="firewall" element={<Firewall />} />
<Route path="porthijack" element={<PortHijack />} />
<Route path="*" element={<HomeRedirector />} />

View File

@@ -1,15 +1,12 @@
import { Button, Group, Space, TextInput, Notification, Switch, NativeSelect, Modal, Alert } from '@mantine/core';
import { Button, Group, Space, TextInput, Notification, Switch, NativeSelect, Modal } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useState } from 'react';
import { RegexAddForm } from '../js/models';
import { b64decode, b64encode, getapiobject, okNotify } from '../js/utils';
import { ImCross } from "react-icons/im"
import FilterTypeSelector from './FilterTypeSelector';
import { AiFillWarning } from 'react-icons/ai';
type RegexAddInfo = {
regex:string,
type:string,
mode:string,
is_case_insensitive:boolean,
deactive:boolean
@@ -20,14 +17,12 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
const form = useForm({
initialValues: {
regex:"",
type:"blacklist",
mode:"C -> S",
is_case_insensitive:false,
deactive:false
},
validate:{
regex: (value) => value !== "" ? null : "Regex is required",
type: (value) => ["blacklist","whitelist"].includes(value) ? null : "Invalid type",
mode: (value) => ['C -> S', 'S -> C', 'C <-> S'].includes(value) ? null : "Invalid mode",
}
})
@@ -46,7 +41,6 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
const filter_mode = ({'C -> S':'C', 'S -> C':'S', 'C <-> S':'B'}[values.mode])
const request:RegexAddForm = {
is_blacklist:values.type !== "whitelist",
is_case_sensitive: !values.is_case_insensitive,
service_id: service,
mode: filter_mode?filter_mode:"B",
@@ -58,7 +52,7 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
if (!res){
setSubmitLoading(false)
close();
okNotify(`Regex ${b64decode(request.regex)} has been added`, `Successfully added ${request.is_case_sensitive?"case sensitive":"case insensitive"} ${request.is_blacklist?"blacklist":"whitelist"} regex to ${request.service_id} service`)
okNotify(`Regex ${b64decode(request.regex)} has been added`, `Successfully added ${request.is_case_sensitive?"case sensitive":"case insensitive"} regex to ${request.service_id} service`)
}else if (res.toLowerCase() === "invalid regex"){
setSubmitLoading(false)
form.setFieldError("regex", "Invalid Regex")
@@ -98,16 +92,6 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
variant="filled"
{...form.getInputProps('mode')}
/>
<Space h="md" />
<FilterTypeSelector
size="md"
color="gray"
{...form.getInputProps('type')}
/>
{form.values.type == "whitelist"?<><Space h="md" />
<Alert variant="light" color="yellow" radius="lg" title="You are using whitelists" icon={<AiFillWarning />}>
Using whitelist means that EVERY packet that doesn't match the regex will be DROPPED... In most cases this cause the service interruption.
</Alert></>:null}
<Group align="right" mt="md">
<Button loading={submitLoading} type="submit">Add Filter</Button>
</Group>

View File

@@ -1,29 +0,0 @@
import { Box, Center, SegmentedControl } from "@mantine/core";
import { FaListAlt } from "react-icons/fa";
import { TiCancel } from "react-icons/ti";
export default function FilterTypeSelector(props:any){
return <SegmentedControl
data={[
{
value: 'blacklist',
label: (
<Center style={{color:"#FFF"}}>
<TiCancel size={23} color="red"/>
<Box ml={10}>Blacklist</Box>
</Center>
),
},
{
value: 'whitelist',
label: (
<Center style={{color:"#FFF"}}>
<FaListAlt size={16} color="gray"/>
<Box ml={10}>Whitelist</Box>
</Center>
),
},
]}
{...props}
/>
}

View File

@@ -39,11 +39,6 @@ export default function NavBar() {
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="lime" icon={<IoMdGitNetwork />} />
<NavBarButton navigate="firewall" closeNav={closeNav} name="Firewall Rules" color="red" icon={<PiWallLight />} />
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="blue" icon={<GrDirections />} />
<Divider my="xs" label="Advanced" labelPosition="center" />
<NavBarButton closeNav={closeNav} name="Deprecated options" color="gray" icon={toggle ? <MdOutlineExpandLess /> : <MdOutlineExpandMore />} onClick={()=>setToggleState(!toggle)}/>
<Collapse in={toggle}>
<NavBarButton navigate="regexproxy" closeNav={closeNav} name="TCP Proxy Regex Filter" color="grape" icon={<MdTransform />} />
</Collapse>
</Box>
</AppShell.Navbar>

View File

@@ -1,6 +1,6 @@
import { ServerResponse } from "../../js/models"
import { getapi, postapi } from "../../js/utils"
import { UseQueryOptions, useQuery } from "@tanstack/react-query"
import { useQuery } from "@tanstack/react-query"
export type GeneralStats = {
services:number

View File

@@ -1,117 +0,0 @@
import { Button, Group, Space, TextInput, Notification, Modal, Switch } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useState } from 'react';
import { okNotify } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { regexproxy } from './utils';
import PortInput from '../PortInput';
type ServiceAddForm = {
name:string,
port:number,
autostart: boolean,
chosenInternalPort: boolean,
internalPort: number
}
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
const form = useForm({
initialValues: {
name:"",
port:8080,
internalPort:30001,
chosenInternalPort:false,
autostart: true
},
validate:{
name: (value) => value !== ""? null : "Service name is required",
port: (value) => (value>0 && value<65536) ? null : "Invalid port",
internalPort: (value) => (value>0 && value<65536) ? null : "Invalid internal port",
}
})
const close = () =>{
onClose()
form.reset()
setError(null)
}
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = ({ name, port, autostart, chosenInternalPort, internalPort }:ServiceAddForm) =>{
setSubmitLoading(true)
regexproxy.servicesadd(chosenInternalPort?{ internalPort, name, port }:{ name, port }).then( res => {
if (res.status === "ok"){
setSubmitLoading(false)
close();
if (autostart) regexproxy.servicestart(res.id)
okNotify(`Service ${name} has been added`, `Successfully added ${res.id} with port ${port}`)
}else{
setSubmitLoading(false)
setError("Invalid request! [ "+res.status+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
return <Modal size="xl" title="Add a new service" opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
<TextInput
label="Service name"
placeholder="Challenge 01"
{...form.getInputProps('name')}
/>
<Space h="md" />
<PortInput
fullWidth
label="Public Service port"
{...form.getInputProps('port')}
/>
{form.values.chosenInternalPort?<>
<Space h="md" />
<PortInput
fullWidth
label="Internal Proxy Port"
{...form.getInputProps('internalPort')}
/>
<Space h="sm" />
</>:null}
<Space h="xl" />
<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })}
/>
<Space h="md" />
<Switch
label="Choose internal port"
{...form.getInputProps('chosenInternalPort', { type: 'checkbox' })}
/>
<Group align="right" mt="md">
<Button loading={submitLoading} type="submit">Add Service</Button>
</Group>
<Space h="md" />
{error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" /></>:null}
</form>
</Modal>
}
export default AddNewService;

View File

@@ -1,101 +0,0 @@
import { Button, Group, Space, Notification, Modal, Center, Title } from '@mantine/core';
import { useForm } from '@mantine/form';
import React, { useEffect, useState } from 'react';
import { ImCross } from "react-icons/im"
import { FaLongArrowAltDown } from 'react-icons/fa';
import { regexproxy, Service } from '../utils';
import { okNotify } from '../../../js/utils';
import PortInput from '../../PortInput';
type InputForm = {
internalPort:number,
port:number
}
function ChangePortModal({ service, opened, onClose }:{ service:Service, opened:boolean, onClose:()=>void }) {
const form = useForm({
initialValues: {
internalPort: service.internal_port,
port: service.public_port
},
validate:{
internalPort: (value) => (value>0 && value<65536) ? null : "Invalid internal port",
port: (value) => (value>0 && value<65536) ? null : "Invalid public port",
}
})
useEffect(()=>{
form.setValues({internalPort: service.internal_port, port:service.public_port})
},[opened])
const close = () =>{
onClose()
form.reset()
setError(null)
}
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = (data:InputForm) =>{
setSubmitLoading(true)
regexproxy.servicechangeport(service.id, data).then( res => {
if (!res){
setSubmitLoading(false)
close();
okNotify(`Internal port on ${service.name} service has changed in ${data.internalPort}`, `Successfully changed internal port of service with id ${service.id}`)
}else{
setSubmitLoading(false)
setError("Invalid request! [ "+res+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
return <Modal size="xl" title="Change Ports" opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
<PortInput
fullWidth
label="Internal Proxy Port"
{...form.getInputProps('internalPort')}
/>
<Space h="xl" />
<Center><FaLongArrowAltDown size={50}/></Center>
<PortInput
fullWidth
label="Public Service Port"
{...form.getInputProps('port')}
/>
<Space h="xl" />
<Center><Title order={5}>The change of the ports will cause a temporarily shutdown of the service! </Title></Center>
<Space h="md" />
<Group align="right" mt="md">
<Button loading={submitLoading} disabled={
service.internal_port === form.values.internalPort && service.public_port === form.values.port
} type="submit">Change Port</Button>
</Group>
<Space h="md" />
{error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" /></>:null}
</form>
</Modal>
}
export default ChangePortModal;

View File

@@ -1,68 +0,0 @@
import { Button, Group, Space, TextInput, Notification, Modal } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useEffect, useState } from 'react';
import { okNotify } from '../../../js/utils';
import { ImCross } from "react-icons/im"
import { regexproxy, Service } from '../utils';
function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:Service }) {
const form = useForm({
initialValues: { name:service.name },
validate:{ name: (value) => value !== ""? null : "Service name is required" }
})
const close = () =>{
onClose()
form.reset()
setError(null)
}
useEffect(()=> form.setFieldValue("name", service.name),[opened])
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = ({ name }:{ name:string }) => {
setSubmitLoading(true)
regexproxy.servicerename(service.id, name).then( res => {
if (!res){
setSubmitLoading(false)
close();
okNotify(`Service ${service.name} has been renamed in ${ name }`, `Successfully renamed service on port ${service.public_port}`)
}else{
setSubmitLoading(false)
setError("Error: [ "+res+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
return <Modal size="xl" title={`Rename '${service.name}' service on port ${service.public_port}`} opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
<TextInput
label="Service Name"
placeholder="Awesome Service Name!"
{...form.getInputProps('name')}
/>
<Group align="right" mt="md">
<Button loading={submitLoading} type="submit">Rename</Button>
</Group>
<Space h="md" />
{error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" /></>:null}
</form>
</Modal>
}
export default RenameForm;

View File

@@ -1,205 +0,0 @@
import { ActionIcon, Badge, Box, Divider, Grid, Menu, Space, Title, Tooltip } from '@mantine/core';
import { useState } from 'react';
import { FaPause, FaPlay, FaStop } from 'react-icons/fa';
import { MdOutlineArrowForwardIos } from "react-icons/md"
import YesNoModal from '../../YesNoModal';
import { errorNotify, isMediumScreen, okNotify } from '../../../js/utils';
import { BsArrowRepeat, BsTrashFill } from 'react-icons/bs';
import { TbNumbers } from 'react-icons/tb';
import { BiRename } from 'react-icons/bi'
import ChangePortModal from './ChangePortModal';
import RenameForm from './RenameForm';
import { regexproxy, Service } from '../utils';
import { MenuDropDownWithButton } from '../../MainLayout';
//"status":"stop"/"wait"/"active"/"pause",
function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
let status_color = "gray";
switch(service.status){
case "stop": status_color = "red"; break;
case "wait": status_color = "yellow"; break;
case "active": status_color = "teal"; break;
case "pause": status_color = "cyan"; break;
}
const [stopModal, setStopModal] = useState(false);
const [buttonLoading, setButtonLoading] = useState(false)
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
const [deleteModal, setDeleteModal] = useState(false)
const [changePortModal, setChangePortModal] = useState(false)
const [choosePortModal, setChoosePortModal] = useState(false)
const [renameModal, setRenameModal] = useState(false)
const isMedium = isMediumScreen()
const stopService = async () => {
setButtonLoading(true)
await regexproxy.servicestop(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} stopped successfully!`,`The service ${service.name} has been stopped!`)
}else{
errorNotify(`An error as occurred during the stopping of the service ${service.id}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the stopping of the service ${service.id}`,`Error: ${err}`)
})
setButtonLoading(false);
}
const startService = async () => {
setButtonLoading(true)
await regexproxy.servicestart(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} started successfully!`,`The service ${service.name} has been started!`)
}else{
errorNotify(`An error as occurred during the starting of the service ${service.id}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the starting of the service ${service.id}`,`Error: ${err}`)
})
setButtonLoading(false)
}
const pauseService = async () => {
setButtonLoading(true)
await regexproxy.servicepause(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} paused successfully!`,`The service ${service.name} has been paused (Transparent mode)!`)
}else{
errorNotify(`An error as occurred during the pausing of the service ${service.id}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the pausing of the service ${service.id}`,`Error: ${err}`)
})
setButtonLoading(false)
}
const deleteService = () => {
regexproxy.servicedelete(service.id).then(res => {
if (!res){
okNotify("Service delete complete!",`The service ${service.id} has been deleted!`)
}else
errorNotify("An error occurred while deleting a service",`Error: ${res}`)
}).catch(err => {
errorNotify("An error occurred while deleting a service",`Error: ${err}`)
})
}
const changePort = () => {
regexproxy.serviceregenport(service.id).then(res => {
if (!res){
okNotify("Service port regeneration completed!",`The service ${service.id} has changed the internal port!`)
}else
errorNotify("An error occurred while changing the internal service port",`Error: ${res}`)
}).catch(err => {
errorNotify("An error occurred while changing the internal service port",`Error: ${err}`)
})
}
return <>
<Grid className="firegex__servicerow__row" justify="flex-end" style={{width:"100%"}}>
<Grid.Col span={{ md:4, xs: 12 }}>
<Box className={isMedium?"center-flex-row":"center-flex"}>
<Box className="center-flex"><Title className="firegex__servicerow__name">{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.public_port}</Badge></Box>
<Badge color={status_color} size="lg" radius="md">{service.internal_port} {"->"} {service.public_port}</Badge>
</Box>
{!isMedium?<Space h="xl" />:null}
</Grid.Col>
<Grid.Col className="center-flex" span={{ md:8, xs: 12 }}>
{!isMedium?<Box className='flex-spacer' />:<><Space w="xl" /><Space w="xl" /></>}
<Box className="center-flex-row">
<Badge style={{marginBottom:"20px"}} color={status_color} radius="sm" size="lg" variant="filled">Status: <u>{service.status}</u></Badge>
<Badge style={{marginBottom:"8px"}}color="violet" radius="sm" size="md" variant="filled">Regex: {service.n_regex}</Badge>
<Badge color="yellow" radius="sm" size="md" variant="filled">Connections Blocked: {service.n_packets}</Badge>
</Box>
{isMedium?<Box className='flex-spacer' />:<><Space w="xl" /><Space w="xl" /></>}
<Box className="center-flex">
<MenuDropDownWithButton>
<Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item leftSection={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Divider />
<Menu.Label><b>Change ports</b></Menu.Label>
<Menu.Item leftSection={<TbNumbers size={18} />} onClick={()=>setChoosePortModal(true)}>Change port</Menu.Item>
<Menu.Item leftSection={<BsArrowRepeat size={18} />} onClick={()=>setChangePortModal(true)}>Regen proxy port</Menu.Item>
<Divider />
<Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" leftSection={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</MenuDropDownWithButton>
<Space w="md"/>
{["pause","wait"].includes(service.status)?
<Tooltip label="Stop service" zIndex={0} color="orange" opened={tooltipStopOpened}>
<ActionIcon color="yellow" loading={buttonLoading}
onClick={()=>setStopModal(true)} size="xl" radius="md" variant="filled"
disabled={service.status === "stop"}
aria-describedby="tooltip-stop-id"
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
<FaStop size="20px" />
</ActionIcon>
</Tooltip>:
<Tooltip label={service.status === "stop"?"Start in pause mode":"Pause service"} zIndex={0} color={service.status === "stop"?"cyan":"red"}>
<ActionIcon color={service.status === "stop"?"cyan":"red"} loading={buttonLoading}
onClick={pauseService} size="xl" radius="md" variant="filled">
<FaPause size="20px" />
</ActionIcon>
</Tooltip>
}
<Space w="md"/>
<Tooltip label="Start service" zIndex={0} color="teal">
<ActionIcon color="teal" size="xl" radius="md" onClick={startService} loading={buttonLoading}
variant="filled" disabled={!["stop","pause"].includes(service.status)?true:false}>
<FaPlay size="20px" />
</ActionIcon>
</Tooltip>
</Box>
<Space w="xl" /><Space w="xl" />
{onClick?<Box>
<MdOutlineArrowForwardIos onClick={onClick} style={{cursor:"pointer"}} size={45} />
<Space w="xl" />
</Box>:null}
{!isMedium?<><Space w="xl" /><Space w="xl" /></>:null}
</Grid.Col>
</Grid>
<YesNoModal
title='Are you sure to stop this service?'
description={`You are going to delete the service '${service.id}', causing the firewall to stop. This will cause the shutdown of your service! ⚠️`}
onClose={()=>{setStopModal(false);}}
action={stopService}
opened={stopModal}
/>
<Divider size="md" style={{width:"100%"}}/>
<YesNoModal
title='Are you sure to delete this service?'
description={`You are going to delete the service '${service.id}', causing the stopping of the firewall and deleting all the regex associated. This will cause the shutdown of your service! ⚠️`}
onClose={()=>setDeleteModal(false) }
action={deleteService}
opened={deleteModal}
/>
<YesNoModal
title='Are you sure to change the proxy internal port?'
description={`You are going to change the proxy port '${service.internal_port}'. This will cause the shutdown of your service temporarily! ⚠️`}
onClose={()=>setChangePortModal(false)}
action={changePort}
opened={changePortModal}
/>
<ChangePortModal
service={service}
onClose={()=> setChoosePortModal(false)}
opened={choosePortModal}
/>
<RenameForm
onClose={()=>setRenameModal(false)}
opened={renameModal}
service={service}
/>
</>
}
export default ServiceRow;

View File

@@ -1,97 +0,0 @@
import { useQuery } from "@tanstack/react-query"
import { RegexAddForm, RegexFilter, ServerResponse } from "../../js/models"
import { getapi, postapi } from "../../js/utils"
export type Service = {
id:string,
name:string,
status:string,
public_port:number,
internal_port:number,
n_packets:number,
n_regex:number,
}
export type ServiceAddForm = {
name:string,
port:number,
internalPort?:number
}
export type ServerResponseWithID = {
status:string,
id:string
}
export type ChangePort = {
port?: number,
internalPort?: number
}
export const serviceQueryKey = ["regexproxy","services"]
export const regexproxyServiceQuery = () => useQuery({queryKey:serviceQueryKey, queryFn:regexproxy.services})
export const regexproxyServiceRegexesQuery = (service_id:string) => useQuery({
queryKey:[...serviceQueryKey,service_id,"regexes"],
queryFn:() => regexproxy.serviceregexes(service_id)
})
export const regexproxy = {
services: async() => {
return await getapi("regexproxy/services") as Service[];
},
serviceinfo: async (service_id:string) => {
return await getapi(`regexproxy/service/${service_id}`) as Service;
},
regexdelete: async (regex_id:number) => {
const { status } = await getapi(`regexproxy/regex/${regex_id}/delete`) as ServerResponse;
return status === "ok"?undefined:status
},
regexenable: async (regex_id:number) => {
const { status } = await getapi(`regexproxy/regex/${regex_id}/enable`) as ServerResponse;
return status === "ok"?undefined:status
},
regexdisable: async (regex_id:number) => {
const { status } = await getapi(`regexproxy/regex/${regex_id}/disable`) as ServerResponse;
return status === "ok"?undefined:status
},
servicestart: async (service_id:string) => {
const { status } = await getapi(`regexproxy/service/${service_id}/start`) as ServerResponse;
return status === "ok"?undefined:status
},
servicestop: async (service_id:string) => {
const { status } = await getapi(`regexproxy/service/${service_id}/stop`) as ServerResponse;
return status === "ok"?undefined:status
},
servicepause: async (service_id:string) => {
const { status } = await getapi(`regexproxy/service/${service_id}/pause`) as ServerResponse;
return status === "ok"?undefined:status
},
serviceregenport: async (service_id:string) => {
const { status } = await getapi(`regexproxy/service/${service_id}/regen-port`) as ServerResponse;
return status === "ok"?undefined:status
},
servicechangeport: async (service_id:string, data:ChangePort) => {
const { status } = await postapi(`regexproxy/service/${service_id}/change-ports`,data) as ServerResponse;
return status === "ok"?undefined:status
},
servicesadd: async (data:ServiceAddForm) => {
return await postapi("regexproxy/services/add",data) as ServerResponseWithID;
},
servicerename: async (service_id:string, name: string) => {
const { status } = await postapi(`regexproxy/service/${service_id}/rename`,{ name }) as ServerResponse;
return status === "ok"?undefined:status
},
servicedelete: async (service_id:string) => {
const { status } = await getapi(`regexproxy/service/${service_id}/delete`) as ServerResponse;
return status === "ok"?undefined:status
},
regexesadd: async (data:RegexAddForm) => {
const { status } = await postapi("regexproxy/regexes/add",data) as ServerResponse;
return status === "ok"?undefined:status
},
serviceregexes: async (service_id:string) => {
return await getapi(`regexproxy/service/${service_id}/regexes`) as RegexFilter[];
}
}

View File

@@ -4,7 +4,6 @@ import { RegexFilter } from '../../js/models';
import { b64decode, errorNotify, getapiobject, okNotify } from '../../js/utils';
import { BsTrashFill } from "react-icons/bs"
import YesNoModal from '../YesNoModal';
import FilterTypeSelector from '../FilterTypeSelector';
import { FaPause, FaPlay } from 'react-icons/fa';
import { useClipboard } from '@mantine/hooks';
@@ -74,12 +73,6 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
</Grid.Col>
<Grid.Col className='center-flex' span={12}>
<Box className='center-flex-row'>
<FilterTypeSelector
size="md"
color="gray"
disabled
value={regexInfo.is_blacklist?"blacklist":"whitelist"}
/>
<Space h="md" />
<Box className='center-flex'>
<Badge size="md" color="cyan" variant="filled">Service: {regexInfo.service_id}</Badge>

View File

@@ -47,7 +47,6 @@ export type RegexAddForm = {
service_id:string,
regex:string,
is_case_sensitive:boolean,
is_blacklist:boolean,
mode:string, // C->S S->C BOTH,
active: boolean
}

View File

@@ -3,7 +3,6 @@ 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 { regexproxy } from "../components/RegexProxy/utils";
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
import { Buffer } from "buffer"
import { QueryClient, useQuery } from "@tanstack/react-query";
@@ -111,8 +110,6 @@ export function getapiobject(){
switch(getMainPath()){
case "nfregex":
return nfregex
case "regexproxy":
return regexproxy
}
throw new Error('No api for this tool!');
}

View File

@@ -1,48 +0,0 @@
import { ActionIcon, Box, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import { useState } from 'react';
import { Navigate, useParams } from 'react-router-dom';
import { BsPlusLg } from "react-icons/bs";
import { regexproxyServiceQuery, regexproxyServiceRegexesQuery } from '../../components/RegexProxy/utils';
import ServiceRow from '../../components/RegexProxy/ServiceRow';
import AddNewRegex from '../../components/AddNewRegex';
import RegexView from '../../components/RegexView';
function ServiceDetailsProxyRegex() {
const {srv} = useParams()
const [open, setOpen] = useState(false)
const services = regexproxyServiceQuery()
const serviceInfo = services.data?.find(s => s.id == srv)
const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false)
const regexesList = regexproxyServiceRegexesQuery(srv??"")
if (!srv || !serviceInfo || regexesList.isError) return <Navigate to="/" replace />
return <Box>
<LoadingOverlay visible={regexesList.isLoading} />
<ServiceRow service={serviceInfo} />
{(!regexesList.data || regexesList.data.length == 0)?<>
<Space h="xl" />
<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" />
<Box className='center-flex'>
<Tooltip label="Add a new regex" zIndex={0} color="blue" opened={tooltipAddRegexOpened}>
<ActionIcon color="blue" onClick={()=>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)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip>
</Box>
</>:
<Grid>
{regexesList.data.map( (regexInfo) => <Grid.Col key={regexInfo.id} span={{ lg:6, xs: 12 }}><RegexView regexInfo={regexInfo} /></Grid.Col>)}
</Grid>
}
{srv?<AddNewRegex opened={open} onClose={() => {setOpen(false)}} service={srv} />:null}
</Box>
}
export default ServiceDetailsProxyRegex;

View File

@@ -1,91 +0,0 @@
import { ActionIcon, Badge, Box, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import { useEffect, useState } from 'react';
import { BsPlusLg } from "react-icons/bs";
import { useNavigate, useParams } from 'react-router-dom';
import ServiceRow from '../../components/RegexProxy/ServiceRow';
import { regexproxyServiceQuery } from '../../components/RegexProxy/utils';
import { errorNotify, getErrorMessage } from '../../js/utils';
import AddNewService from '../../components/RegexProxy/AddNewService';
import AddNewRegex from '../../components/AddNewRegex';
import { useQueryClient } from '@tanstack/react-query';
import { TbReload } from 'react-icons/tb';
function RegexProxy({ children }: { children: any }) {
const navigator = useNavigate()
const [open, setOpen] = useState(false);
const {srv} = useParams()
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
const queryClient = useQueryClient()
const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false);
const services = regexproxyServiceQuery()
useEffect(()=> {
if(services.isError){
errorNotify("RegexProxy Update failed!", getErrorMessage(services.error))
}
},[services.isError])
const closeModal = () => {setOpen(false);}
return <>
<Space h="sm" />
<Box className='center-flex'>
<Title order={4}>TCP Proxy Regex Filter (IPv4 Only)</Title>
<Box className='flex-spacer' />
<Badge size="sm" color="green" variant="filled">Services: {services.isLoading?0:services.data?.length}</Badge>
<Space w="xs" />
<Badge size="sm" color="yellow" variant="filled">Filtered Connections: {services.isLoading?0:services.data?.reduce((acc, s)=> acc+=s.n_packets, 0)}</Badge>
<Space w="xs" />
<Badge size="sm" color="violet" variant="filled">Regexes: {services.isLoading?0:services.data?.reduce((acc, s)=> acc+=s.n_regex, 0)}</Badge>
<Space w="xs" />
{ srv?
<Tooltip label="Add a new regex" position='bottom' color="blue" opened={tooltipAddOpened}>
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
</Tooltip>
: <Tooltip label="Add a new service" position='bottom' color="blue" opened={tooltipAddOpened}>
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
</Tooltip>
}
<Space w="xs" />
<Tooltip label="Refresh" position='bottom' color="indigo" opened={tooltipRefreshOpened}>
<ActionIcon color="indigo" onClick={()=>queryClient.invalidateQueries(["regexproxy"])} size="lg" radius="md" variant="filled"
loading={services.isFetching}
onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)}
onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}><TbReload size={18} /></ActionIcon>
</Tooltip>
</Box>
<Box className="center-flex-row">
{srv?null:<>
<LoadingOverlay visible={services.isLoading} />
{(services.data && services.data?.length > 0)?services.data.map( srv => <ServiceRow service={srv} key={srv.id} onClick={()=>{
navigator("/regexproxy/"+srv.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" /> <Space h="xl" />
<Box className='center-flex'>
<Tooltip label="Add a new service" color="blue" opened={tooltipAddServOpened}>
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)}
onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip>
</Box>
</>}
<AddNewService opened={open} onClose={closeModal} />
</>}
</Box>
{srv?children:null}
{srv?
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
<AddNewService opened={open} onClose={closeModal} />
}
</>
}
export default RegexProxy;

View File

@@ -1,60 +1,95 @@
#!/usr/bin/env python3
from utils.colors import *
from utils.firegexapi import *
import argparse, secrets
from utils.colors import colors, puts, sep
from utils.firegexapi import FiregexAPI
import argparse
import secrets
parser = argparse.ArgumentParser()
parser.add_argument("--address", "-a", type=str , required=False, help='Address of firegex backend', default="http://127.0.0.1:4444/")
parser.add_argument("--password", "-p", type=str, required=True, help='Firegex password')
args = parser.parse_args()
sep()
puts(f"Testing will start on ", color=colors.cyan, end="")
puts("Testing will start on ", color=colors.cyan, end="")
puts(f"{args.address}", color=colors.yellow)
firegex = FiregexAPI(args.address)
#Connect to Firegex
if firegex.status()["status"] =="init":
if (firegex.set_password(args.password)): puts(f"Sucessfully set password to {args.password}", color=colors.green)
else: puts(f"Test Failed: Unknown response or password already put ✗", color=colors.red); exit(1)
if (firegex.set_password(args.password)):
puts(f"Sucessfully set password to {args.password}", color=colors.green)
else:
puts("Test Failed: Unknown response or password already put ✗", color=colors.red)
exit(1)
else:
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
if (firegex.login(args.password)):
puts("Sucessfully logged in ✔", color=colors.green)
else:
puts("Test Failed: Unknown response or wrong passowrd ✗", color=colors.red)
exit(1)
if(firegex.status()["loggined"]): puts(f"Correctly received status ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or not logged in✗", color=colors.red); exit(1)
if(firegex.status()["loggined"]):
puts("Correctly received status ✔", color=colors.green)
else:
puts("Test Failed: Unknown response or not logged in✗", color=colors.red)
exit(1)
#Prepare second instance
firegex2 = FiregexAPI(args.address)
if (firegex2.login(args.password)): puts(f"Sucessfully logged in on second instance ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or wrong passowrd on second instance ", color=colors.red); exit(1)
if (firegex2.login(args.password)):
puts("Sucessfully logged in on second instance ", color=colors.green)
else:
puts("Test Failed: Unknown response or wrong passowrd on second instance ✗", color=colors.red)
exit(1)
if(firegex2.status()["loggined"]): puts(f"Correctly received status on second instance✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or not logged in on second instance", color=colors.red); exit(1)
if(firegex2.status()["loggined"]):
puts("Correctly received status on second instance", color=colors.green)
else:
puts("Test Failed: Unknown response or not logged in on second instance✗", color=colors.red)
exit(1)
#Change password
new_password = secrets.token_hex(10)
if (firegex.change_password(new_password,expire=True)): puts(f"Sucessfully changed password to {new_password}", color=colors.green)
else: puts(f"Test Failed: Coundl't change the password", color=colors.red); exit(1)
if (firegex.change_password(new_password,expire=True)):
puts(f"Sucessfully changed password to {new_password}", color=colors.green)
else:
puts("Test Failed: Coundl't change the password ✗", color=colors.red)
exit(1)
#Check if we are still logged in
if(firegex.status()["loggined"]): puts(f"Correctly received status after password change ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or not logged after password change ", color=colors.red); exit(1)
if(firegex.status()["loggined"]):
puts("Correctly received status after password change ", color=colors.green)
else:
puts("Test Failed: Unknown response or not logged after password change ✗", color=colors.red)
exit(1)
#Check if second session expired and relog
if(not firegex2.status()["loggined"]): puts(f"Second instance was expired currectly ✔", color=colors.green)
else: puts(f"Test Failed: Still logged in on second instance, expire expected ✗", color=colors.red); exit(1)
if (firegex2.login(new_password)): puts(f"Sucessfully logged in on second instance ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or wrong passowrd on second instance ✗", color=colors.red); exit(1)
if(not firegex2.status()["loggined"]):
puts("Second instance was expired currectly ✔", color=colors.green)
else:
puts("Test Failed: Still logged in on second instance, expire expected", color=colors.red)
exit(1)
if (firegex2.login(new_password)):
puts("Sucessfully logged in on second instance ✔", color=colors.green)
else:
puts("Test Failed: Unknown response or wrong passowrd on second instance ✗", color=colors.red)
exit(1)
#Change it back
if (firegex.change_password(args.password,expire=False)): puts(f"Sucessfully restored the password ✔", color=colors.green)
else: puts(f"Test Failed: Coundl't change the password ", color=colors.red); exit(1)
if (firegex.change_password(args.password,expire=False)):
puts("Sucessfully restored the password ", color=colors.green)
else:
puts("Test Failed: Coundl't change the password ✗", color=colors.red)
exit(1)
#Check if we are still logged in
if(firegex2.status()["loggined"]): puts(f"Correctly received status after password change ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or not logged after password change ", color=colors.red); exit(1)
if(firegex2.status()["loggined"]):
puts("Correctly received status after password change ", color=colors.green)
else:
puts("Test Failed: Unknown response or not logged after password change ✗", color=colors.red)
exit(1)
puts("List of available interfaces:", color=colors.yellow)
for interface in firegex.get_interfaces(): puts("name: {}, address: {}".format(interface["name"], interface["addr"]), color=colors.yellow)
for interface in firegex.get_interfaces():
puts("name: {}, address: {}".format(interface["name"], interface["addr"]), color=colors.yellow)

View File

@@ -1,24 +1,25 @@
#!/usr/bin/env python3
from utils.colors import *
from utils.firegexapi import *
from utils.tcpserver import *
from utils.colors import colors, puts, sep
from utils.firegexapi import FiregexAPI
from multiprocessing import Process
from time import sleep
import iperf3, csv, argparse, base64, secrets
import iperf3
import csv
import argparse
import base64
import secrets
#TODO: make it work with Proxy and not only netfilter
parser = argparse.ArgumentParser()
parser.add_argument("--address", "-a", type=str , required=False, help='Address of firegex backend', default="http://127.0.0.1:4444/")
parser.add_argument("--port", "-P", type=int , required=False, help='Port of the Benchmark service', default=1337)
parser.add_argument("--internal-port", "-I", type=int , required=False, help='Internal port of the Benchmark service', default=1338)
parser.add_argument("--service-name", "-n", type=str , required=False, help='Name of the Benchmark service', default="Benchmark Service")
parser.add_argument("--password", "-p", type=str, required=True, help='Firegex password')
parser.add_argument("--num-of-regexes", "-r", type=int, required=True, help='Number of regexes to benchmark with')
parser.add_argument("--duration", "-d", type=int, required=False, help='Duration of the Benchmark in seconds', default=5)
parser.add_argument("--output-file", "-o", type=str, required=False, help='Output results csv file', default="benchmark.csv")
parser.add_argument("--num-of-streams", "-s", type=int, required=False, help='Output results csv file', default=1)
parser.add_argument("--mode", "-m" , type=str, required=True, choices=["netfilter","proxy"], help='Type of filtering')
args = parser.parse_args()
sep()
@@ -28,22 +29,36 @@ puts(f"{args.address}", color=colors.yellow)
firegex = FiregexAPI(args.address)
#Connect to Firegex
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
else: puts(f"Benchmark Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
if (firegex.login(args.password)):
puts("Sucessfully logged in ✔", color=colors.green)
else:
puts("Benchmark Failed: Unknown response or wrong passowrd ✗", color=colors.red)
exit(1)
def exit_test(code):
if service_id:
server.stop()
if(firegex.nf_delete_service(service_id)):
puts("Sucessfully deleted service ✔", color=colors.green)
else:
puts("Test Failed: Coulnd't delete serivce ✗", color=colors.red)
exit_test(1)
exit(code)
#Create new Service
if args.mode == "netfilter":
service_id = firegex.nf_add_service(args.service_name, args.port, "tcp", "127.0.0.1/24")
service_id = firegex.nf_add_service(args.service_name, args.port, "tcp", "127.0.0.1/24")
if service_id:
puts(f"Sucessfully created service {service_id}", color=colors.green)
else:
service_id = firegex.px_add_service(args.service_name, args.port, internalPort=args.internal_port)
if service_id: puts(f"Sucessfully created service {service_id}", color=colors.green)
else: puts(f"Test Failed: Failed to create service ✗", color=colors.red); exit(1)
puts("Test Failed: Failed to create service ✗", color=colors.red)
exit(1)
#Start iperf3
def startServer():
server = iperf3.Server()
server.bind_address = '127.0.0.1'
server.port = args.port if args.mode == "netfilter" else args.internal_port
server.port = args.port
server.verbose = False
while True:
server.run()
@@ -63,27 +78,29 @@ sleep(1)
#Get baseline reading
puts(f"Baseline without proxy: ", color=colors.blue, end='')
print(f"{getReading(args.port if args.mode == 'netfilter' else args.internal_port)} MB/s")
puts("Baseline without proxy: ", color=colors.blue, end='')
print(f"{getReading(args.port)} MB/s")
#Start firewall
if(firegex.nf_start_service(service_id) if args.mode == "netfilter" else firegex.px_start_service(service_id)):
if firegex.nf_start_service(service_id):
puts(f"Sucessfully started service with id {service_id}", color=colors.green)
else:
puts(f"Benchmark Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
puts("Benchmark Failed: Coulnd't start the service ✗", color=colors.red)
exit_test(1)
#Get no regexs reading
results = []
puts(f"Performance with no regexes: ", color=colors.yellow , end='')
puts("Performance with no regexes: ", color=colors.yellow , end='')
results.append(getReading(args.port))
print(f"{results[0]} MB/s")
#Add all the regexs
for i in range(1,args.num_of_regexes+1):
regex = base64.b64encode(bytes(secrets.token_hex(16).encode())).decode()
if(not (firegex.nf_add_regex if args.mode == "netfilter" else firegex.px_add_regex)(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=False) ):
puts(f"Benchmark Failed: Coulnd't add the regex ✗", color=colors.red); exit_test(1)
if not firegex.nf_add_regex(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=False):
puts("Benchmark Failed: Coulnd't add the regex ✗", color=colors.red)
exit_test(1)
puts(f"Performance with {i} regex(s): ", color=colors.red, end='')
results.append(getReading(args.port))
print(f"{results[i]} MB/s")
@@ -96,9 +113,10 @@ with open(args.output_file,'w') as f:
puts(f"Sucessfully written results to {args.output_file}", color=colors.magenta)
#Delete the Service
if(firegex.nf_delete_service(service_id) if args.mode == "netfilter" else firegex.px_delete_service(service_id)):
if firegex.nf_delete_service(service_id):
puts(f"Sucessfully delete service with id {service_id}", color=colors.green)
else:
puts(f"Test Failed: Couldn't delete service ✗", color=colors.red); exit(1)
puts("Test Failed: Couldn't delete service ✗", color=colors.red)
exit(1)
server.terminate()

View File

@@ -1,9 +1,12 @@
#!/usr/bin/env python3
from utils.colors import *
from utils.firegexapi import *
from utils.colors import colors, puts, sep
from utils.firegexapi import FiregexAPI
from utils.tcpserver import TcpServer
from utils.udpserver import UdpServer
import argparse, secrets, base64,time
import argparse
import secrets
import base64
import time
parser = argparse.ArgumentParser()
parser.add_argument("--address", "-a", type=str , required=False, help='Address of firegex backend', default="http://127.0.0.1:4444/")
@@ -15,14 +18,17 @@ parser.add_argument("--proto", "-m" , type=str, required=False, choices=["tcp","
args = parser.parse_args()
sep()
puts(f"Testing will start on ", color=colors.cyan, end="")
puts("Testing will start on ", color=colors.cyan, end="")
puts(f"{args.address}", color=colors.yellow)
firegex = FiregexAPI(args.address)
#Login
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
if (firegex.login(args.password)):
puts("Sucessfully logged in ✔", color=colors.green)
else:
puts("Test Failed: Unknown response or wrong passowrd ✗", color=colors.red)
exit(1)
#Create server
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port,ipv6=args.ipv6)
@@ -30,30 +36,42 @@ server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port,ipv6=args.i
def exit_test(code):
if service_id:
server.stop()
if(firegex.nf_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
else: puts(f"Test Failed: Coulnd't delete serivce ", color=colors.red); exit_test(1)
if(firegex.nf_delete_service(service_id)):
puts("Sucessfully deleted service ", color=colors.green)
else:
puts("Test Failed: Coulnd't delete serivce ✗", color=colors.red)
exit_test(1)
exit(code)
service_id = firegex.nf_add_service(args.service_name, args.port, args.proto , "::1" if args.ipv6 else "127.0.0.1" )
if service_id: puts(f"Sucessfully created service {service_id}", color=colors.green)
else: puts(f"Test Failed: Failed to create service ", color=colors.red); exit(1)
if service_id:
puts("Sucessfully created service {service_id}", color=colors.green)
else:
puts("Test Failed: Failed to create service ✗", color=colors.red)
exit(1)
if(firegex.nf_start_service(service_id)): puts(f"Sucessfully started service ✔", color=colors.green)
else: puts(f"Test Failed: Failed to start service ", color=colors.red); exit_test(1)
if(firegex.nf_start_service(service_id)):
puts("Sucessfully started service ", color=colors.green)
else:
puts("Test Failed: Failed to start service ✗", color=colors.red)
exit_test(1)
server.start()
time.sleep(0.5)
if server.sendCheckData(secrets.token_bytes(432)):
puts(f"Successfully tested first proxy with no regex ✔", color=colors.green)
puts("Successfully tested first proxy with no regex ✔", color=colors.green)
else:
puts(f"Test Failed: Data was corrupted ", color=colors.red); exit_test(1)
puts("Test Failed: Data was corrupted ", color=colors.red)
exit_test(1)
#Add new regex
secret = bytes(secrets.token_hex(16).encode())
regex = base64.b64encode(secret).decode()
if(firegex.nf_add_regex(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=True)):
if firegex.nf_add_regex(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=True):
puts(f"Sucessfully added regex {str(secret)}", color=colors.green)
else: puts(f"Test Failed: Coulnd't add the regex {str(secret)}", color=colors.red); exit_test(1)
else:
puts("Test Failed: Coulnd't add the regex {str(secret)} ✗", color=colors.red)
exit_test(1)
#Check if regex is present in the service
n_blocked = 0
@@ -66,35 +84,45 @@ def checkRegex(regex, should_work=True, upper=False):
#Test the regex
s = base64.b64decode(regex).upper() if upper else base64.b64decode(regex)
if not server.sendCheckData(secrets.token_bytes(200) + s + secrets.token_bytes(200)):
puts(f"The malicious request was successfully blocked ✔", color=colors.green)
puts("The malicious request was successfully blocked ✔", color=colors.green)
n_blocked += 1
time.sleep(1)
if firegex.nf_get_regex(r["id"])["n_packets"] == n_blocked:
puts(f"The packed was reported as blocked ✔", color=colors.green)
puts("The packed was reported as blocked ✔", color=colors.green)
else:
puts(f"Test Failed: The packed wasn't reported as blocked ✗", color=colors.red); exit_test(1)
puts("Test Failed: The packed wasn't reported as blocked ✗", color=colors.red)
exit_test(1)
else:
puts(f"Test Failed: The request wasn't blocked ✗", color=colors.red);exit_test(1)
puts("Test Failed: The request wasn't blocked ✗", color=colors.red)
exit_test(1)
return
puts(f"Test Failed: The regex wasn't found ✗", color=colors.red); exit_test(1)
puts("Test Failed: The regex wasn't found ✗", color=colors.red)
exit_test(1)
else:
if server.sendCheckData(secrets.token_bytes(200) + base64.b64decode(regex) + secrets.token_bytes(200)):
puts(f"The request wasn't blocked ✔", color=colors.green)
puts("The request wasn't blocked ✔", color=colors.green)
else:
puts(f"Test Failed: The request was blocked when it shouldn't have", color=colors.red); exit_test(1)
puts("Test Failed: The request was blocked when it shouldn't have", color=colors.red)
exit_test(1)
checkRegex(regex)
#Pause the proxy
if(firegex.nf_stop_service(service_id)): puts(f"Sucessfully paused service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't pause the service ✗", color=colors.red); exit_test(1)
if(firegex.nf_stop_service(service_id)):
puts(f"Sucessfully paused service with id {service_id}", color=colors.green)
else:
puts("Test Failed: Coulnd't pause the service ✗", color=colors.red)
exit_test(1)
#Check if it's actually paused
checkRegex(regex,should_work=False)
#Start firewall
if(firegex.nf_start_service(service_id)): puts(f"Sucessfully started service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
if(firegex.nf_start_service(service_id)):
puts(f"Sucessfully started service with id {service_id}", color=colors.green)
else:
puts("Test Failed: Coulnd't start the service ✗", color=colors.red)
exit_test(1)
checkRegex(regex)
@@ -104,7 +132,8 @@ for r in firegex.nf_get_service_regexes(service_id):
if(firegex.nf_disable_regex(r["id"])):
puts(f"Sucessfully disabled regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't disable the regex ✗", color=colors.red); exit_test(1)
puts("Test Failed: Coulnd't disable the regex ✗", color=colors.red)
exit_test(1)
break
#Check if it's actually disabled
@@ -116,7 +145,8 @@ for r in firegex.nf_get_service_regexes(service_id):
if(firegex.nf_enable_regex(r["id"])):
puts(f"Sucessfully enabled regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't enable the regex ✗", color=colors.red); exit_test(1)
puts("Test Failed: Coulnd't enable the regex ✗", color=colors.red)
exit_test(1)
break
checkRegex(regex)
@@ -128,7 +158,8 @@ for r in firegex.nf_get_service_regexes(service_id):
if(firegex.nf_delete_regex(r["id"])):
puts(f"Sucessfully deleted regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't delete the regex ✗", color=colors.red); exit_test(1)
puts("Test Failed: Coulnd't delete the regex ✗", color=colors.red)
exit_test(1)
break
#Check if it's actually deleted
@@ -137,7 +168,9 @@ checkRegex(regex,should_work=False)
#Add case insensitive regex
if(firegex.nf_add_regex(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=False)):
puts(f"Sucessfully added case insensitive regex {str(secret)}", color=colors.green)
else: puts(f"Test Failed: Coulnd't add the case insensitive regex {str(secret)}", color=colors.red); exit_test(1)
else:
puts(f"Test Failed: Coulnd't add the case insensitive regex {str(secret)}", color=colors.red)
exit_test(1)
checkRegex(regex,upper=True)
checkRegex(regex)
@@ -149,36 +182,22 @@ for r in firegex.nf_get_service_regexes(service_id):
if(firegex.nf_delete_regex(r["id"])):
puts(f"Sucessfully deleted regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't delete the regex ✗", color=colors.red); exit_test(1)
break
#Add whitelist regex
if(firegex.nf_add_regex(service_id,regex,"B",active=True,is_blacklist=False,is_case_sensitive=True)):
puts(f"Sucessfully added case whitelist regex {str(secret)}", color=colors.green)
else: puts(f"Test Failed: Coulnd't add the case whiteblist regex {str(secret)}", color=colors.red); exit_test(1)
checkRegex(regex,should_work=False)
checkRegex(regex,upper=True) #Dirty way to test the whitelist :p
#Delete regex
n_blocked = 0
for r in firegex.nf_get_service_regexes(service_id):
if r["regex"] == regex:
if(firegex.nf_delete_regex(r["id"])):
puts(f"Sucessfully deleted regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't delete the regex ✗", color=colors.red); exit_test(1)
puts("Test Failed: Coulnd't delete the regex ✗", color=colors.red)
exit_test(1)
break
#Rename service
if(firegex.nf_rename_service(service_id,f"{args.service_name}2")): puts(f"Sucessfully renamed service to {args.service_name}2 ✔", color=colors.green)
else: puts(f"Test Failed: Coulnd't rename service ", color=colors.red); exit_test(1)
if(firegex.nf_rename_service(service_id,f"{args.service_name}2")):
puts(f"Sucessfully renamed service to {args.service_name}2 ✔", color=colors.green)
else:
puts("Test Failed: Coulnd't rename service ✗", color=colors.red)
exit_test(1)
#Check if service was renamed correctly
for services in firegex.nf_get_services():
if services["name"] == f"{args.service_name}2":
puts(f"Checked that service was renamed correctly ✔", color=colors.green)
puts("Checked that service was renamed correctly ✔", color=colors.green)
exit_test(0)
puts(f"Test Failed: Service wasn't renamed correctly ✗", color=colors.red); exit_test(1)
puts("Test Failed: Service wasn't renamed correctly ✗", color=colors.red)
exit_test(1)

View File

@@ -1,9 +1,11 @@
#!/usr/bin/env python3
from utils.colors import *
from utils.firegexapi import *
from utils.colors import colors, puts, sep
from utils.firegexapi import FiregexAPI
from utils.tcpserver import TcpServer
from utils.udpserver import UdpServer
import argparse, secrets, base64,time
import argparse
import secrets
import time
parser = argparse.ArgumentParser()
parser.add_argument("--address", "-a", type=str , required=False, help='Address of firegex backend', default="http://127.0.0.1:4444/")
@@ -15,14 +17,17 @@ parser.add_argument("--proto", "-m" , type=str, required=False, choices=["tcp","
args = parser.parse_args()
sep()
puts(f"Testing will start on ", color=colors.cyan, end="")
puts("Testing will start on ", color=colors.cyan, end="")
puts(f"{args.address}", color=colors.yellow)
firegex = FiregexAPI(args.address)
#Login
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
if (firegex.login(args.password)):
puts("Sucessfully logged in ✔", color=colors.green)
else:
puts("Test Failed: Unknown response or wrong passowrd ✗", color=colors.red)
exit(1)
#Create server
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port+1,ipv6=args.ipv6,proxy_port=args.port)
@@ -30,17 +35,26 @@ server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port+1,ipv6=args
def exit_test(code):
if service_id:
server.stop()
if(firegex.ph_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
else: puts(f"Test Failed: Coulnd't delete serivce ", color=colors.red); exit_test(1)
if(firegex.ph_delete_service(service_id)):
puts("Sucessfully deleted service ", color=colors.green)
else:
puts("Test Failed: Coulnd't delete serivce ✗", color=colors.red)
exit_test(1)
exit(code)
#Create and start serivce
service_id = firegex.ph_add_service(args.service_name, args.port, args.port+1, args.proto , "::1" if args.ipv6 else "127.0.0.1", "::1" if args.ipv6 else "127.0.0.1")
if service_id: puts(f"Sucessfully created service {service_id}", color=colors.green)
else: puts(f"Test Failed: Failed to create service ", color=colors.red); exit(1)
if service_id:
puts("Sucessfully created service {service_id}", color=colors.green)
else:
puts("Test Failed: Failed to create service ✗", color=colors.red)
exit(1)
if(firegex.ph_start_service(service_id)): puts(f"Sucessfully started service ✔", color=colors.green)
else: puts(f"Test Failed: Failed to start service ", color=colors.red); exit_test(1)
if(firegex.ph_start_service(service_id)):
puts("Sucessfully started service ", color=colors.green)
else:
puts("Test Failed: Failed to start service ✗", color=colors.red)
exit_test(1)
server.start()
time.sleep(0.5)
@@ -48,33 +62,49 @@ time.sleep(0.5)
#Check if it started
def checkData(should_work):
res = None
try: res = server.sendCheckData(secrets.token_bytes(432))
except (ConnectionRefusedError, TimeoutError): res = None
try:
res = server.sendCheckData(secrets.token_bytes(432))
except (ConnectionRefusedError, TimeoutError):
res = None
if res:
if should_work: puts(f"Successfully received data ✔", color=colors.green)
else: puts("Test Failed: Connection wasn't blocked ✗", color=colors.red); exit_test(1)
if should_work:
puts("Successfully received data ✔", color=colors.green)
else:
if should_work: puts(f"Test Failed: Data wans't received ✗", color=colors.red); exit_test(1)
else: puts(f"Successfully blocked connection ✔", color=colors.green)
puts("Test Failed: Connection wasn't blocked ✗", color=colors.red)
exit_test(1)
else:
if should_work:
puts("Test Failed: Data wans't received ✗", color=colors.red)
exit_test(1)
else:
puts("Successfully blocked connection ✔", color=colors.green)
checkData(True)
#Pause the proxy
if(firegex.ph_stop_service(service_id)): puts(f"Sucessfully paused service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't pause the service ✗", color=colors.red); exit_test(1)
if(firegex.ph_stop_service(service_id)):
puts(f"Sucessfully paused service with id {service_id}", color=colors.green)
else:
puts("Test Failed: Coulnd't pause the service ✗", color=colors.red)
exit_test(1)
checkData(False)
#Start firewall
if(firegex.ph_start_service(service_id)): puts(f"Sucessfully started service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
if(firegex.ph_start_service(service_id)):
puts(f"Sucessfully started service with id {service_id}", color=colors.green)
else:
puts("Test Failed: Coulnd't start the service ✗", color=colors.red)
exit_test(1)
checkData(True)
#Change port
if(firegex.ph_change_destination(service_id, "::1" if args.ipv6 else "127.0.0.1", args.port+2)):
puts(f"Sucessfully changed port ✔", color=colors.green)
else: puts(f"Test Failed: Coulnd't change destination ✗", color=colors.red); exit_test(1)
puts("Sucessfully changed port ✔", color=colors.green)
else:
puts("Test Failed: Coulnd't change destination ✗", color=colors.red)
exit_test(1)
checkData(False)
@@ -86,14 +116,17 @@ time.sleep(0.5)
checkData(True)
#Rename service
if(firegex.ph_rename_service(service_id,f"{args.service_name}2")): puts(f"Sucessfully renamed service to {args.service_name}2 ✔", color=colors.green)
else: puts(f"Test Failed: Coulnd't rename service ", color=colors.red); exit_test(1)
if(firegex.ph_rename_service(service_id,f"{args.service_name}2")):
puts(f"Sucessfully renamed service to {args.service_name}2 ✔", color=colors.green)
else:
puts("Test Failed: Coulnd't rename service ✗", color=colors.red)
exit_test(1)
#Check if service was renamed correctly
for services in firegex.ph_get_services():
if services["name"] == f"{args.service_name}2":
puts(f"Checked that service was renamed correctly ✔", color=colors.green)
puts("Checked that service was renamed correctly ✔", color=colors.green)
exit_test(0)
puts(f"Test Failed: Service wasn't renamed correctly ✗", color=colors.red); exit_test(1)
puts("Test Failed: Service wasn't renamed correctly ✗", color=colors.red)
exit_test(1)

View File

@@ -1,249 +0,0 @@
#!/usr/bin/env python3
from utils.colors import *
from utils.firegexapi import *
from utils.tcpserver import TcpServer
import argparse, secrets, base64,time,random
parser = argparse.ArgumentParser()
parser.add_argument("--address", "-a", type=str , required=False, help='Address of firegex backend', default="http://127.0.0.1:4444/")
parser.add_argument("--password", "-p", type=str, required=True, help='Firegex password')
parser.add_argument("--service_name", "-n", type=str , required=False, help='Name of the test service', default="Test Service")
parser.add_argument("--port", "-P", type=int , required=False, help='Port of the test service', default=1337)
args = parser.parse_args()
sep()
puts(f"Testing will start on ", color=colors.cyan, end="")
puts(f"{args.address}", color=colors.yellow)
#Create and start server
server = TcpServer(args.port,ipv6=False)
server.start()
time.sleep(0.5)
firegex = FiregexAPI(args.address)
#Login
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
def exit_test(code):
if service_id:
server.stop()
if(firegex.px_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
else: puts(f"Test Failed: Coulnd't deleted serivce ✗", color=colors.red); exit_test(1)
exit(code)
#Create service
service_id = firegex.px_add_service(args.service_name, args.port, 6140)
if service_id: puts(f"Sucessfully created service {service_id}", color=colors.green)
else: puts(f"Test Failed: Failed to create service ✗", color=colors.red); exit(1)
if(firegex.px_start_service(service_id)): puts(f"Sucessfully started service ✔", color=colors.green)
else: puts(f"Test Failed: Failed to start service ✗", color=colors.red); exit_test(1)
#Check if service is in wait mode
if(firegex.px_get_service(service_id)["status"] == "wait"): puts(f"Sucessfully started service in WAIT mode ✔", color=colors.green)
else: puts(f"Test Failed: Service not in WAIT mode ✗", color=colors.red); exit_test(1)
#Get inernal_port
internal_port = firegex.px_get_service(service_id)["internal_port"]
if (internal_port): puts(f"Sucessfully got internal port {internal_port}", color=colors.green)
else: puts(f"Test Failed: Coundn't get internal_port ✗", color=colors.red); exit_test(1)
server.stop()
server = TcpServer(internal_port,ipv6=False, proxy_port=args.port)
server.start()
time.sleep(1)
if(firegex.px_get_service(service_id)["status"] == "active"): puts(f"Service went in ACTIVE mode ✔", color=colors.green)
else: puts(f"Test Failed: Service not in ACTIVE mode ✗", color=colors.red); exit_test(1)
if server.sendCheckData(secrets.token_bytes(432)):
puts(f"Successfully tested first proxy with no regex ✔", color=colors.green)
else:
puts(f"Test Failed: Data was corrupted ", color=colors.red); exit_test(1)
#Add new regex
secret = bytes(secrets.token_hex(16).encode())
regex = base64.b64encode(secret).decode()
if(firegex.px_add_regex(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=True)):
puts(f"Sucessfully added regex {str(secret)}", color=colors.green)
else: puts(f"Test Failed: Coulnd't add the regex {str(secret)}", color=colors.red); exit_test(1)
#Check if regex is present in the service
n_blocked = 0
def checkRegex(regex, should_work=True, upper=False):
if should_work:
global n_blocked
for r in firegex.px_get_service_regexes(service_id):
if r["regex"] == regex:
#Test the regex
s = base64.b64decode(regex).upper() if upper else base64.b64decode(regex)
if not server.sendCheckData(secrets.token_bytes(200) + s + secrets.token_bytes(200)):
puts(f"The malicious request was successfully blocked ✔", color=colors.green)
n_blocked += 1
time.sleep(0.5)
if firegex.px_get_regex(r["id"])["n_packets"] == n_blocked:
puts(f"The packed was reported as blocked ✔", color=colors.green)
else:
puts(f"Test Failed: The packed wasn't reported as blocked ✗", color=colors.red); exit_test(1)
else:
puts(f"Test Failed: The request wasn't blocked ✗", color=colors.red);exit_test(1)
return
puts(f"Test Failed: The regex wasn't found ✗", color=colors.red); exit_test(1)
else:
if server.sendCheckData(secrets.token_bytes(200) + base64.b64decode(regex) + secrets.token_bytes(200)):
puts(f"The request wasn't blocked ✔", color=colors.green)
else:
puts(f"Test Failed: The request was blocked when it shouldn't have", color=colors.red); exit_test(1)
checkRegex(regex)
#Pause the proxy
if(firegex.px_pause_service(service_id)): puts(f"Sucessfully paused service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't pause the service ✗", color=colors.red); exit_test(1)
#Check if it's actually paused
checkRegex(regex,should_work=False)
#Start firewall
if(firegex.px_start_service(service_id)): puts(f"Sucessfully started service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
checkRegex(regex)
#Stop firewall
if(firegex.px_stop_service(service_id)): puts(f"Sucessfully stopped service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't stop the service ✗", color=colors.red); exit_test(1)
try:
checkRegex(regex)
puts(f"Test Failed: The service was still active ✗", color=colors.red); exit_test(1)
except Exception:
puts(f"Service was correctly stopped ✔", color=colors.green)
#Start firewall in pause
if(firegex.px_pause_service(service_id)): puts(f"Sucessfully started service in pause mode with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
time.sleep(0.5)
#Check if it's actually paused
checkRegex(regex,should_work=False)
#Start firewall
if(firegex.px_start_service(service_id)): puts(f"Sucessfully started service with id {service_id}", color=colors.green)
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
checkRegex(regex)
#Disable regex
for r in firegex.px_get_service_regexes(service_id):
if r["regex"] == regex:
if(firegex.px_disable_regex(r["id"])):
puts(f"Sucessfully disabled regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't disable the regex ✗", color=colors.red); exit_test(1)
break
#Check if it's actually disabled
checkRegex(regex,should_work=False)
#Enable regex
for r in firegex.px_get_service_regexes(service_id):
if r["regex"] == regex:
if(firegex.px_enable_regex(r["id"])):
puts(f"Sucessfully enabled regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't enable the regex ✗", color=colors.red); exit_test(1)
break
checkRegex(regex)
#Delete regex
n_blocked = 0
for r in firegex.px_get_service_regexes(service_id):
if r["regex"] == regex:
if(firegex.px_delete_regex(r["id"])):
puts(f"Sucessfully deleted regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't delete the regex ✗", color=colors.red); exit_test(1)
break
#Check if it's actually deleted
checkRegex(regex,should_work=False)
#Add case insensitive regex
if(firegex.px_add_regex(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=False)):
puts(f"Sucessfully added case insensitive regex {str(secret)}", color=colors.green)
else: puts(f"Test Failed: Coulnd't add the case insensitive regex {str(secret)}", color=colors.red); exit_test(1)
checkRegex(regex,upper=True)
checkRegex(regex)
#Delete regex
n_blocked = 0
for r in firegex.px_get_service_regexes(service_id):
if r["regex"] == regex:
if(firegex.px_delete_regex(r["id"])):
puts(f"Sucessfully deleted regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't delete the regex ✗", color=colors.red); exit_test(1)
break
#Add whitelist regex
if(firegex.px_add_regex(service_id,regex,"B",active=True,is_blacklist=False,is_case_sensitive=True)):
puts(f"Sucessfully added case whitelist regex {str(secret)}", color=colors.green)
else: puts(f"Test Failed: Coulnd't add the case whiteblist regex {str(secret)}", color=colors.red); exit_test(1)
checkRegex(regex,should_work=False)
checkRegex(regex,upper=True) #Dirty way to test the whitelist :p
#Delete regex
n_blocked = 0
for r in firegex.px_get_service_regexes(service_id):
if r["regex"] == regex:
if(firegex.px_delete_regex(r["id"])):
puts(f"Sucessfully deleted regex with id {r['id']}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't delete the regex ✗", color=colors.red); exit_test(1)
break
#Rename service
if(firegex.px_rename_service(service_id,f"{args.service_name}2")): puts(f"Sucessfully renamed service to {args.service_name}2 ✔", color=colors.green)
else: puts(f"Test Failed: Coulnd't rename service ✗", color=colors.red); exit_test(1)
#Check if service was renamed correctly
found = False
for services in firegex.px_get_services():
if services["name"] == f"{args.service_name}2":
puts(f"Checked that service was renamed correctly ✔", color=colors.green)
found = True
break
if not found:
puts(f"Test Failed: Service wasn't renamed correctly ✗", color=colors.red); exit_test(1)
exit(1)
#Change service port
new_internal_port = random.randrange(6000,9000)
if(firegex.px_change_service_port(service_id,internalPort=new_internal_port)):
puts(f"Sucessfully changed internal_port to {new_internal_port}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't change intenral port ✗", color=colors.red); exit_test(1)
#Get inernal_port
internal_port = firegex.px_get_service(service_id)["internal_port"]
if (internal_port == new_internal_port): puts(f"Sucessfully got internal port {internal_port}", color=colors.green)
else: puts(f"Test Failed: Coundn't get internal_port or port changed incorrectly ✗", color=colors.red); exit_test(1)
if(firegex.px_regen_service_port(service_id)):
puts(f"Sucessfully changed internal_port to {new_internal_port}", color=colors.green)
else:
puts(f"Test Failed: Coulnd't change internal port ✗", color=colors.red); exit_test(1)
#Get regenerated inernal_port
new_internal_port = firegex.px_get_service(service_id)["internal_port"]
if (internal_port != new_internal_port): puts(f"Sucessfully got regenerated port {new_internal_port}", color=colors.green)
else: puts(f"Test Failed: Coundn't get internal port, or it was the same as previous ✗", color=colors.red); exit_test(1)
exit_test(0)

View File

@@ -18,8 +18,6 @@ echo "Running Netfilter Regex UDP ipv4"
python3 nf_test.py -p $PASSWORD -m udp || ERROR=1
echo "Running Netfilter Regex UDP ipv6"
python3 nf_test.py -p $PASSWORD -m udp -6 || ERROR=1
echo "Running Proxy Regex"
python3 px_test.py -p $PASSWORD || ERROR=1
echo "Running Port Hijack TCP ipv4"
python3 ph_test.py -p $PASSWORD -m tcp || ERROR=1
echo "Running Port Hijack TCP ipv6"

View File

@@ -70,7 +70,7 @@ class FiregexAPI:
return req.json()
def reset(self, delete: bool):
req = self.s.post(f"{self.address}api/reset", json={"delete":delete})
self.s.post(f"{self.address}api/reset", json={"delete":delete})
#Netfilter regex
def nf_get_stats(self):
@@ -131,84 +131,6 @@ class FiregexAPI:
json={"name":name,"port":port, "proto": proto, "ip_int": ip_int})
return req.json()["service_id"] if verify(req) else False
#Proxy regex
def px_get_stats(self):
req = self.s.get(f"{self.address}api/regexproxy/stats")
return req.json()
def px_get_services(self):
req = self.s.get(f"{self.address}api/regexproxy/services")
return req.json()
def px_get_service(self,service_id: str):
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}")
return req.json()
def px_stop_service(self,service_id: str):
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/stop")
return verify(req)
def px_pause_service(self,service_id: str):
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/pause")
return verify(req)
def px_start_service(self,service_id: str):
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/start")
return verify(req)
def px_delete_service(self,service_id: str):
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/delete")
return verify(req)
def px_regen_service_port(self,service_id: str):
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regen-port")
return verify(req)
def px_change_service_port(self,service_id: str, port:int =None, internalPort:int =None):
payload = {}
if port: payload["port"] = port
if internalPort: payload["internalPort"] = internalPort
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/change-ports", json=payload)
return req.json() if verify(req) else False
def px_get_service_regexes(self,service_id: str):
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regexes")
return req.json()
def px_get_regex(self,regex_id: str):
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}")
return req.json()
def px_delete_regex(self,regex_id: str):
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/delete")
return verify(req)
def px_enable_regex(self,regex_id: str):
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/enable")
return verify(req)
def px_disable_regex(self,regex_id: str):
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/disable")
return verify(req)
def px_add_regex(self, service_id: str, regex: str, mode: str, active: bool, is_blacklist: bool, is_case_sensitive: bool):
req = self.s.post(f"{self.address}api/regexproxy/regexes/add",
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_blacklist": is_blacklist, "is_case_sensitive": is_case_sensitive})
return verify(req)
def px_rename_service(self,service_id: str, newname: str):
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/rename" , json={"name":newname})
return verify(req)
def px_add_service(self, name: str, port: int, internalPort:int = None):
payload = {}
payload["name"] = name
payload["port"] = port
if internalPort:
payload["internalPort"] = internalPort
req = self.s.post(f"{self.address}api/regexproxy/services/add" , json=payload)
return req.json()["id"] if verify(req) else False
#PortHijack
def ph_get_services(self):
req = self.s.get(f"{self.address}api/porthijack/services")