Merge pull request #12 from Pwnzer0tt1/dev-cpp
Implementing new cpp nfqueue with hyperscan an stream regex assembling TCP packets with libtis
This commit is contained in:
13
.gitignore
vendored
13
.gitignore
vendored
@@ -19,12 +19,13 @@
|
|||||||
/frontend/dist/
|
/frontend/dist/
|
||||||
/frontend/dist/**
|
/frontend/dist/**
|
||||||
/backend/modules/cppqueue
|
/backend/modules/cppqueue
|
||||||
|
/backend/binsrc/cppqueue
|
||||||
/backend/modules/proxy
|
/backend/modules/proxy
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
firegex-compose.yml
|
firegex-compose.yml
|
||||||
firegex-compose-tmp-file.yml
|
firegex-compose-tmp-file.yml
|
||||||
firegex.py
|
firegex.py
|
||||||
|
/tests/benchmark.csv
|
||||||
# misc
|
# misc
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/.env.local
|
**/.env.local
|
||||||
@@ -35,13 +36,3 @@ firegex.py
|
|||||||
**/npm-debug.log*
|
**/npm-debug.log*
|
||||||
**/yarn-debug.log*
|
**/yarn-debug.log*
|
||||||
**/yarn-error.log*
|
**/yarn-error.log*
|
||||||
|
|
||||||
#rust
|
|
||||||
/backend/binsrc/nfqueue_regex/debug/
|
|
||||||
/backend/binsrc/nfqueue_regex/target/
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
|
||||||
**/*.rs.bk
|
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
|
||||||
**/*.pdb
|
|
||||||
|
|||||||
18
Dockerfile
18
Dockerfile
@@ -7,25 +7,18 @@
|
|||||||
FROM --platform=$BUILDPLATFORM oven/bun AS frontend
|
FROM --platform=$BUILDPLATFORM oven/bun AS frontend
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD ./frontend/package.json .
|
ADD ./frontend/package.json .
|
||||||
ADD ./frontend/bun.lockb .
|
ADD ./frontend/bun.lock .
|
||||||
RUN bun i
|
RUN bun i
|
||||||
COPY ./frontend/ .
|
COPY ./frontend/ .
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
|
|
||||||
#Building main conteiner
|
#Building main conteiner
|
||||||
FROM --platform=$TARGETARCH debian:stable-slim AS base
|
FROM --platform=$TARGETARCH debian:trixie-slim AS base
|
||||||
RUN apt-get update -qq && apt-get upgrade -qq && \
|
RUN apt-get update -qq && apt-get upgrade -qq && \
|
||||||
apt-get install -qq python3-pip build-essential \
|
apt-get install -qq python3-pip build-essential \
|
||||||
git libpcre2-dev libnetfilter-queue-dev libssl-dev \
|
libnetfilter-queue-dev libnfnetlink-dev libmnl-dev libcap2-bin\
|
||||||
libnfnetlink-dev libmnl-dev libcap2-bin make cmake \
|
nftables libvectorscan-dev libtins-dev python3-nftables
|
||||||
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
|
|
||||||
|
|
||||||
RUN mkdir -p /execute/modules
|
RUN mkdir -p /execute/modules
|
||||||
WORKDIR /execute
|
WORKDIR /execute
|
||||||
@@ -34,8 +27,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
|
RUN pip3 install --no-cache-dir --break-system-packages -r /execute/requirements.txt --no-warn-script-location
|
||||||
|
|
||||||
COPY ./backend/binsrc /execute/binsrc
|
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/nfqueue.cpp -o modules/cppqueue -O3 -lnetfilter_queue -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libhs libmnl)
|
||||||
RUN g++ binsrc/proxy.cpp -o modules/proxy -O3 -pthread -lboost_system -lboost_thread -lpcre2-8
|
|
||||||
|
|
||||||
COPY ./backend/ /execute/
|
COPY ./backend/ /execute/
|
||||||
COPY --from=frontend /app/dist/ ./frontend/
|
COPY --from=frontend /app/dist/ ./frontend/
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ All the configuration at the startup is customizable in [firegex.py](./start.py)
|
|||||||
- Create basic firewall rules to allow and deny specific traffic, like ufw or iptables but using firegex graphic interface (by using [nftable](https://netfilter.org/projects/nftables/))
|
- Create basic firewall rules to allow and deny specific traffic, like ufw or iptables but using firegex graphic interface (by using [nftable](https://netfilter.org/projects/nftables/))
|
||||||
- Port Hijacking allows you to redirect the traffic on a specific port to another port. Thanks to this you can start your own proxy, connecting to the real service using the loopback interface. Firegex will be resposable about the routing of the packets using internally [nftables](https://netfilter.org/projects/nftables/)
|
- Port Hijacking allows you to redirect the traffic on a specific port to another port. Thanks to this you can start your own proxy, connecting to the real service using the loopback interface. Firegex will be resposable about the routing of the packets using internally [nftables](https://netfilter.org/projects/nftables/)
|
||||||
|
|
||||||
DEPRECATED:
|
|
||||||
- TCP Proxy regex filter, create a proxy tunnel from the service internal port to a public port published by the proxy. Internally the c++ proxy filter the request with PCRE2 regexes. For mantaining the same public port you will need to open only in localhost the real services. (Available only on TCP/IPv4)
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Find the documentation of the backend and of the frontend in the related README files
|
Find the documentation of the backend and of the frontend in the related README files
|
||||||
@@ -58,7 +55,6 @@ This means that firegex is projected to avoid any possibility to have the servic
|
|||||||
Initiially the project was based only on regex filters, and also now the main function uses regexes, but firegex have and will have also other filtering tools.
|
Initiially the project was based only on regex filters, and also now the main function uses regexes, but firegex have and will have also other filtering tools.
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
- Copyright (c) 2007 Arash Partow (http://www.partow.net) for the base of our proxy implementation
|
|
||||||
- Copyright (c) 2022 Pwnzer0tt1
|
- Copyright (c) 2022 Pwnzer0tt1
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import uvicorn, secrets, utils
|
import uvicorn
|
||||||
import os, asyncio, logging
|
import secrets
|
||||||
from fastapi import FastAPI, HTTPException, Depends, APIRouter, Request
|
import utils
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from fastapi import FastAPI, HTTPException, Depends, APIRouter
|
||||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
@@ -30,7 +34,14 @@ async def lifespan(app):
|
|||||||
yield
|
yield
|
||||||
await shutdown_main()
|
await shutdown_main()
|
||||||
|
|
||||||
app = FastAPI(debug=DEBUG, redoc_url=None, lifespan=lifespan)
|
app = FastAPI(
|
||||||
|
debug=DEBUG,
|
||||||
|
redoc_url=None,
|
||||||
|
lifespan=lifespan,
|
||||||
|
docs_url="/api/docs",
|
||||||
|
title="Firegex API",
|
||||||
|
version=API_VERSION,
|
||||||
|
)
|
||||||
utils.socketio = SocketManager(app, "/sock", socketio_path="")
|
utils.socketio = SocketManager(app, "/sock", socketio_path="")
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@@ -94,7 +105,8 @@ async def get_app_status(auth: bool = Depends(check_login)):
|
|||||||
@app.post("/api/login")
|
@app.post("/api/login")
|
||||||
async def login_api(form: OAuth2PasswordRequestForm = Depends()):
|
async def login_api(form: OAuth2PasswordRequestForm = Depends()):
|
||||||
"""Get a login token to use the firegex api"""
|
"""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 == "":
|
if form.password == "":
|
||||||
return {"status":"Cannot insert an empty password!"}
|
return {"status":"Cannot insert an empty password!"}
|
||||||
await asyncio.sleep(0.3) # No bruteforce :)
|
await asyncio.sleep(0.3) # No bruteforce :)
|
||||||
@@ -105,7 +117,8 @@ async def login_api(form: OAuth2PasswordRequestForm = Depends()):
|
|||||||
@app.post('/api/set-password', response_model=ChangePasswordModel)
|
@app.post('/api/set-password', response_model=ChangePasswordModel)
|
||||||
async def set_password(form: PasswordForm):
|
async def set_password(form: PasswordForm):
|
||||||
"""Set the password of firegex"""
|
"""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 == "":
|
if form.password == "":
|
||||||
return {"status":"Cannot insert an empty password!"}
|
return {"status":"Cannot insert an empty password!"}
|
||||||
set_psw(form.password)
|
set_psw(form.password)
|
||||||
@@ -115,7 +128,8 @@ async def set_password(form: PasswordForm):
|
|||||||
@api.post('/change-password', response_model=ChangePasswordModel)
|
@api.post('/change-password', response_model=ChangePasswordModel)
|
||||||
async def change_password(form: PasswordChangeForm):
|
async def change_password(form: PasswordChangeForm):
|
||||||
"""Change the password of firegex"""
|
"""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 == "":
|
if form.password == "":
|
||||||
return {"status":"Cannot insert an empty password!"}
|
return {"status":"Cannot insert an empty password!"}
|
||||||
@@ -144,7 +158,8 @@ async def startup_main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error setting sysctls: {e}")
|
logging.error(f"Error setting sysctls: {e}")
|
||||||
await startup()
|
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()
|
await refresh_frontend()
|
||||||
|
|
||||||
async def shutdown_main():
|
async def shutdown_main():
|
||||||
@@ -175,9 +190,9 @@ if __name__ == '__main__':
|
|||||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"app:app",
|
"app:app",
|
||||||
host="::" if DEBUG else None,
|
host=None, #"::" if DEBUG else None,
|
||||||
port=FIREGEX_PORT,
|
port=FIREGEX_PORT,
|
||||||
reload=DEBUG,
|
reload=False,#DEBUG,
|
||||||
access_log=True,
|
access_log=True,
|
||||||
workers=1, # Multiple workers will cause a crash due to the creation
|
workers=1, # Multiple workers will cause a crash due to the creation
|
||||||
# of multiple processes with separated memory
|
# of multiple processes with separated memory
|
||||||
|
|||||||
530
backend/binsrc/classes/netfilter.cpp
Normal file
530
backend/binsrc/classes/netfilter.cpp
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
#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 <tins/tcp_ip/stream_identifier.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
|
||||||
|
typedef Tins::TCPIP::StreamIdentifier stream_id;
|
||||||
|
typedef map<stream_id, hs_stream_t*> matching_map;
|
||||||
|
|
||||||
|
/* Considering to use unorder_map using this hash of stream_id
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template<>
|
||||||
|
struct hash<stream_id> {
|
||||||
|
size_t operator()(const stream_id& sid) const
|
||||||
|
{
|
||||||
|
return std::hash<std::uint32_t>()(sid.max_address[0] + sid.max_address[1] + sid.max_address[2] + sid.max_address[3] + sid.max_address_port + sid.min_address[0] + sid.min_address[1] + sid.min_address[2] + sid.min_address[3] + sid.min_address_port);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
ostream& operator<<(ostream& os, const Tins::TCPIP::StreamIdentifier::address_type &sid){
|
||||||
|
bool first_print = false;
|
||||||
|
for (auto ele: sid){
|
||||||
|
if (first_print || ele){
|
||||||
|
first_print = true;
|
||||||
|
os << (int)ele << ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream& operator<<(ostream& os, const stream_id &sid){
|
||||||
|
os << sid.max_address << ":" << sid.max_address_port << " -> " << sid.min_address << ":" << sid.min_address_port;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clean_stream_by_id(stream_id sid){
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.clean_stream_by_id] Cleaning stream context of " << sid << endl;
|
||||||
|
#endif
|
||||||
|
auto stream_search = in_hs_streams.find(sid);
|
||||||
|
hs_stream_t* stream_match;
|
||||||
|
if (stream_search != in_hs_streams.end()){
|
||||||
|
stream_match = stream_search->second;
|
||||||
|
if (hs_close_stream(stream_match, 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");
|
||||||
|
}
|
||||||
|
in_hs_streams.erase(stream_search);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_search = out_hs_streams.find(sid);
|
||||||
|
if (stream_search != out_hs_streams.end()){
|
||||||
|
stream_match = stream_search->second;
|
||||||
|
if (hs_close_stream(stream_match, 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");
|
||||||
|
}
|
||||||
|
out_hs_streams.erase(stream_search);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clean(){
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.clean] Cleaning stream context" << endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (in_scratch){
|
||||||
|
for(auto ele: in_hs_streams){
|
||||||
|
if (hs_close_stream(ele.second, 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in_hs_streams.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_scratch){
|
||||||
|
for(auto ele: out_hs_streams){
|
||||||
|
if (hs_close_stream(ele.second, 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out_hs_streams.clear();
|
||||||
|
}
|
||||||
|
clean_scratches();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct packet_info {
|
||||||
|
string packet;
|
||||||
|
string payload;
|
||||||
|
stream_id sid;
|
||||||
|
bool is_input;
|
||||||
|
bool is_tcp;
|
||||||
|
stream_ctx* sctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef bool NetFilterQueueCallback(packet_info &);
|
||||||
|
|
||||||
|
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" );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_data_recv(Stream& stream, stream_ctx* sctx, string data) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.on_data_recv] data: " << data << endl;
|
||||||
|
#endif
|
||||||
|
sctx->tcp_match_util.matching_has_been_called = true;
|
||||||
|
bool result = callback_func(*sctx->tcp_match_util.pkt_info);
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.on_data_recv] result: " << result << endl;
|
||||||
|
#endif
|
||||||
|
if (!result){
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.on_data_recv] Stream matched, removing all data about it" << endl;
|
||||||
|
#endif
|
||||||
|
sctx->clean_stream_by_id(sctx->tcp_match_util.pkt_info->sid);
|
||||||
|
stream.ignore_client_data();
|
||||||
|
stream.ignore_server_data();
|
||||||
|
}
|
||||||
|
sctx->tcp_match_util.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Input data filtering
|
||||||
|
static void on_client_data(Stream& stream, stream_ctx* sctx) {
|
||||||
|
on_data_recv(stream, sctx, string(stream.client_payload().begin(), stream.client_payload().end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Server data filtering
|
||||||
|
static void on_server_data(Stream& stream, stream_ctx* sctx) {
|
||||||
|
on_data_recv(stream, sctx, string(stream.server_payload().begin(), stream.server_payload().end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_new_stream(Stream& stream, stream_ctx* sctx) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.on_new_stream] New stream detected" << endl;
|
||||||
|
#endif
|
||||||
|
if (stream.is_partial_stream()) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.on_new_stream] Partial stream detected, skipping" << endl;
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stream.auto_cleanup_payloads(true);
|
||||||
|
stream.client_data_callback(bind(on_client_data, placeholders::_1, sctx));
|
||||||
|
stream.server_data_callback(bind(on_server_data, placeholders::_1, sctx));
|
||||||
|
stream.stream_closed_callback(bind(on_stream_close, placeholders::_1, sctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A stream was terminated. The second argument is the reason why it was terminated
|
||||||
|
static void on_stream_close(Stream& stream, stream_ctx* sctx) {
|
||||||
|
stream_id stream_id = stream_id::make_identifier(stream);
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.on_stream_close] Stream terminated, deleting all data" << endl;
|
||||||
|
#endif
|
||||||
|
sctx->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(bind(on_new_stream, placeholders::_1, &sctx));
|
||||||
|
sctx.follower.stream_termination_callback(bind(on_stream_close, placeholders::_1, &sctx));
|
||||||
|
|
||||||
|
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() {
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.~NetfilterQueue] Destructor called" << endl;
|
||||||
|
#endif
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static void build_verdict(T packet, uint8_t *payload, uint16_t plen, nlmsghdr *nlh_verdict, nfqnl_msg_packet_hdr *ph, stream_ctx* sctx, bool is_input){
|
||||||
|
Tins::TCP* tcp = packet.template find_pdu<Tins::TCP>();
|
||||||
|
|
||||||
|
if (tcp){
|
||||||
|
Tins::PDU* application_layer = tcp->inner_pdu();
|
||||||
|
u_int16_t payload_size = 0;
|
||||||
|
if (application_layer != nullptr){
|
||||||
|
payload_size = application_layer->size();
|
||||||
|
}
|
||||||
|
packet_info pktinfo{
|
||||||
|
packet: string(payload, payload+plen),
|
||||||
|
payload: string(payload+plen - payload_size, payload+plen),
|
||||||
|
sid: stream_id::make_identifier(packet),
|
||||||
|
is_input: is_input,
|
||||||
|
is_tcp: true,
|
||||||
|
sctx: sctx,
|
||||||
|
};
|
||||||
|
sctx->tcp_match_util.matching_has_been_called = false;
|
||||||
|
sctx->tcp_match_util.pkt_info = &pktinfo;
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.build_verdict] TCP Packet received " << packet.src_addr() << ":" << tcp->sport() << " -> " << packet.dst_addr() << ":" << tcp->dport() << ", sending to libtins StreamFollower" << endl;
|
||||||
|
#endif
|
||||||
|
sctx->follower.process_packet(packet);
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (sctx->tcp_match_util.matching_has_been_called){
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.build_verdict] StreamFollower has called matching functions" << endl;
|
||||||
|
}else{
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.build_verdict] StreamFollower has NOT called matching functions" << endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (sctx->tcp_match_util.matching_has_been_called && !sctx->tcp_match_util.result){
|
||||||
|
Tins::PDU* data_layer = tcp->release_inner_pdu();
|
||||||
|
if (data_layer != nullptr){
|
||||||
|
delete data_layer;
|
||||||
|
}
|
||||||
|
tcp->set_flag(Tins::TCP::FIN,1);
|
||||||
|
tcp->set_flag(Tins::TCP::ACK,1);
|
||||||
|
tcp->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{
|
||||||
|
Tins::UDP* udp = packet.template find_pdu<Tins::UDP>();
|
||||||
|
if (!udp){
|
||||||
|
throw invalid_argument("Only TCP and UDP are supported");
|
||||||
|
}
|
||||||
|
Tins::PDU* application_layer = udp->inner_pdu();
|
||||||
|
u_int16_t payload_size = 0;
|
||||||
|
if (application_layer != nullptr){
|
||||||
|
payload_size = application_layer->size();
|
||||||
|
}
|
||||||
|
if((udp->inner_pdu() == nullptr)){
|
||||||
|
nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT );
|
||||||
|
}
|
||||||
|
packet_info pktinfo{
|
||||||
|
packet: string(payload, payload+plen),
|
||||||
|
payload: string(payload+plen - payload_size, payload+plen),
|
||||||
|
sid: stream_id::make_identifier(packet),
|
||||||
|
is_input: is_input,
|
||||||
|
is_tcp: false,
|
||||||
|
sctx: sctx,
|
||||||
|
};
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (attr[NFQA_MARK] == nullptr) {
|
||||||
|
fputs("mark 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));
|
||||||
|
|
||||||
|
bool is_input = ntohl(mnl_attr_get_u32(attr[NFQA_MARK])) & 0x1; // == 0x1337 that is odd
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.queue_cb] Packet received" << endl;
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.queue_cb] Packet ID: " << ntohl(ph->packet_id) << endl;
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.queue_cb] Payload size: " << plen << endl;
|
||||||
|
cerr << "[DEBUG] [NetfilterQueue.queue_cb] Is input: " << is_input << endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Check IP protocol version
|
||||||
|
if ( (payload[0] & 0xf0) == 0x40 ){
|
||||||
|
build_verdict(Tins::IP(payload, plen), payload, plen, nlh_verdict, ph, sctx, is_input);
|
||||||
|
}else{
|
||||||
|
build_verdict(Tins::IPv6(payload, plen), payload, plen, nlh_verdict, ph, sctx, is_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
nest = mnl_attr_nest_start(nlh_verdict, NFQA_CT);
|
||||||
|
mnl_attr_put_u32(nlh_verdict, CTA_MARK, htonl(42));
|
||||||
|
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
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
174
backend/binsrc/classes/regex_rules.cpp
Normal file
174
backend/binsrc/classes/regex_rules.cpp
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#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 = nullptr;
|
||||||
|
vector<string> 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);
|
||||||
|
output_ruleset.hs_db = nullptr;
|
||||||
|
}
|
||||||
|
if (input_ruleset.hs_db != nullptr){
|
||||||
|
hs_free_database(input_ruleset.hs_db);
|
||||||
|
input_ruleset.hs_db = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
vector<const char*> regex_match_rules(n_of_regex);
|
||||||
|
vector<unsigned int> regex_array_ids(n_of_regex);
|
||||||
|
vector<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [RegexRules.fill_ruleset] compiling " << n_of_regex << " regexes..." << endl;
|
||||||
|
for (int i = 0; i < n_of_regex; i++){
|
||||||
|
cerr << "[DEBUG] [RegexRules.fill_ruleset] regex[" << i << "]: " << decoded[i].first << " " << decoded[i].second.regex << endl;
|
||||||
|
cerr << "[DEBUG] [RegexRules.fill_ruleset] regex_match_rules[" << i << "]: " << regex_match_rules[i] << endl;
|
||||||
|
cerr << "[DEBUG] [RegexRules.fill_ruleset] regex_flags[" << i << "]: " << regex_flags[i] << endl;
|
||||||
|
cerr << "[DEBUG] [RegexRules.fill_ruleset] regex_array_ids[" << i << "]: " << regex_array_ids[i] << endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
hs_database_t* rebuilt_db = nullptr;
|
||||||
|
hs_compile_error_t *compile_err = nullptr;
|
||||||
|
if (
|
||||||
|
hs_compile_multi(
|
||||||
|
regex_match_rules.data(),
|
||||||
|
regex_flags.data(),
|
||||||
|
regex_array_ids.data(),
|
||||||
|
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;
|
||||||
|
ruleset.regexes = vector<string>(n_of_regex);
|
||||||
|
for(int i = 0; i < n_of_regex; i++){
|
||||||
|
ruleset.regexes[i] = decoded[i].first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
#include "classes/regex_filter.hpp"
|
#include "classes/regex_rules.cpp"
|
||||||
#include "classes/netfilter.hpp"
|
#include "classes/netfilter.cpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
shared_ptr<regex_rules> regex_config;
|
shared_ptr<RegexRules> regex_config;
|
||||||
|
|
||||||
void config_updater (){
|
void config_updater (){
|
||||||
string line;
|
string line;
|
||||||
@@ -21,44 +21,158 @@ void config_updater (){
|
|||||||
}
|
}
|
||||||
cerr << "[info] [updater] Updating configuration with line " << line << endl;
|
cerr << "[info] [updater] Updating configuration with line " << line << endl;
|
||||||
istringstream config_stream(line);
|
istringstream config_stream(line);
|
||||||
regex_rules *regex_new_config = new regex_rules();
|
vector<string> raw_rules;
|
||||||
|
|
||||||
while(!config_stream.eof()){
|
while(!config_stream.eof()){
|
||||||
string data;
|
string data;
|
||||||
config_stream >> data;
|
config_stream >> data;
|
||||||
if (data != "" && data != "\n"){
|
if (data != "" && data != "\n"){
|
||||||
regex_new_config->add(data.c_str());
|
raw_rules.push_back(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
regex_config.reset(regex_new_config);
|
try{
|
||||||
cerr << "[info] [updater] Config update done" << endl;
|
regex_config.reset(new RegexRules(raw_rules, regex_config->stream_mode()));
|
||||||
|
cerr << "[info] [updater] Config update done to ver "<< regex_config->ver() << endl;
|
||||||
|
cout << "ACK OK" << endl;
|
||||||
|
}catch(const std::exception& e){
|
||||||
|
cerr << "[error] [updater] Failed to build new configuration!" << endl;
|
||||||
|
cout << "ACK FAIL " << e.what() << endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool is_input>
|
void inline scratch_setup(regex_ruleset &conf, hs_scratch_t* & scratch){
|
||||||
bool filter_callback(const uint8_t *data, uint32_t len){
|
if (scratch == nullptr && conf.hs_db != nullptr){
|
||||||
shared_ptr<regex_rules> current_config = regex_config;
|
if (hs_alloc_scratch(conf.hs_db, &scratch) != HS_SUCCESS) {
|
||||||
return current_config->check((unsigned char *)data, len, is_input);
|
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;
|
||||||
|
auto current_version = conf->ver();
|
||||||
|
if (current_version != info.sctx->latest_config_ver){
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [filter_callback] Configuration has changed (" << current_version << "!=" << info.sctx->latest_config_ver << "), cleaning scratch spaces" << endl;
|
||||||
|
#endif
|
||||||
|
info.sctx->clean();
|
||||||
|
info.sctx->latest_config_ver = current_version;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [filter_callback] Matching packet with " << (info.is_input ? "input" : "output") << " ruleset" << endl;
|
||||||
|
if (info.payload.size() <= 30){
|
||||||
|
cerr << "[DEBUG] [filter_callback] Packet: " << info.payload << endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
hs_stream_t* stream_match;
|
||||||
|
if (conf->stream_mode()){
|
||||||
|
matching_map* match_map = info.is_input ? &info.sctx->in_hs_streams : &info.sctx->out_hs_streams;
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [filter_callback] Dumping match_map " << match_map << endl;
|
||||||
|
for (auto ele: *match_map){
|
||||||
|
cerr << "[DEBUG] [filter_callback] " << ele.first << " -> " << ele.second << endl;
|
||||||
|
}
|
||||||
|
cerr << "[DEBUG] [filter_callback] End of match_map" << endl;
|
||||||
|
#endif
|
||||||
|
auto stream_search = match_map->find(info.sid);
|
||||||
|
|
||||||
|
if (stream_search == match_map->end()){
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [filter_callback] Creating new stream matcher for " << info.sid << endl;
|
||||||
|
#endif
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
if (info.is_tcp){
|
||||||
|
match_map->insert_or_assign(info.sid, stream_match);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
stream_match = stream_search->second;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [filter_callback] Matching as a stream" << endl;
|
||||||
|
#endif
|
||||||
|
err = hs_scan_stream(
|
||||||
|
stream_match,info.payload.c_str(), info.payload.length(),
|
||||||
|
0, scratch_space, match_func, &match_res
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
#ifdef DEBUG
|
||||||
|
cerr << "[DEBUG] [filter_callback] Matching as a block" << endl;
|
||||||
|
#endif
|
||||||
|
err = hs_scan(
|
||||||
|
regex_matcher,info.payload.c_str(), info.payload.length(),
|
||||||
|
0, scratch_space, match_func, &match_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!info.is_tcp && conf->stream_mode() &&
|
||||||
|
hs_close_stream(stream_match, scratch_space, nullptr, nullptr) != HS_SUCCESS
|
||||||
|
){
|
||||||
|
cerr << "[error] [filter_callback] Error closing the stream matcher (hs)" << endl;
|
||||||
|
throw invalid_argument("Cannot close stream match on hyperscan");
|
||||||
|
}
|
||||||
|
if (err != HS_SUCCESS && err != HS_SCAN_TERMINATED) {
|
||||||
|
cerr << "[error] [filter_callback] Error while matching the stream (hs)" << endl;
|
||||||
|
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;
|
int n_of_threads = 1;
|
||||||
char * n_threads_str = getenv("NTHREADS");
|
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 <= 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;
|
char * matchmode = getenv("MATCH_MODE");
|
||||||
cerr << "[info] [main] Input queues: " << input_queues.init() << ":" << input_queues.end() << " threads assigned: " << n_of_threads/2 << endl;
|
bool stream_mode = true;
|
||||||
cerr << "[info] [main] Output queues: " << output_queues.init() << ":" << output_queues.end() << " threads assigned: " << n_of_threads/2 << endl;
|
if (matchmode != nullptr && strcmp(matchmode, "block") == 0){
|
||||||
|
stream_mode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 << " stream mode: " << stream_mode << endl;
|
||||||
|
|
||||||
config_updater();
|
config_updater();
|
||||||
}
|
}
|
||||||
|
|||||||
32
backend/binsrc/nfqueue_regex/Cargo.lock
generated
32
backend/binsrc/nfqueue_regex/Cargo.lock
generated
@@ -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",
|
|
||||||
]
|
|
||||||
@@ -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"
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -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 ®ex_c_s_b; break;
|
|
||||||
case 'c': // Client to server Whitelist
|
|
||||||
return ®ex_c_s_w; break;
|
|
||||||
case 'S': // Server to client Blacklist
|
|
||||||
return ®ex_s_c_b; break;
|
|
||||||
case 's': // Server to client Whitelist
|
|
||||||
return ®ex_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;
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ bool unhexlify(std::string const &hex, std::string &newString) {
|
|||||||
for(int i=0; i< len; i+=2)
|
for(int i=0; i< len; i+=2)
|
||||||
{
|
{
|
||||||
std::string byte = hex.substr(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);
|
newString.push_back(chr);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from modules.firewall.nftables import FiregexTables
|
from modules.firewall.nftables import FiregexTables
|
||||||
from modules.firewall.models import *
|
from modules.firewall.models import Rule, FirewallSettings
|
||||||
from utils.sqlite import SQLite
|
from utils.sqlite import SQLite
|
||||||
from modules.firewall.models import Action
|
from modules.firewall.models import Action
|
||||||
|
|
||||||
@@ -131,5 +131,5 @@ class FirewallManager:
|
|||||||
return self.db.get("allow_dhcp", "1") == "1"
|
return self.db.get("allow_dhcp", "1") == "1"
|
||||||
|
|
||||||
@drop_invalid.setter
|
@drop_invalid.setter
|
||||||
def allow_dhcp(self, value):
|
def allow_dhcp_set(self, value):
|
||||||
self.db.set("allow_dhcp", "1" if value else "0")
|
self.db.set("allow_dhcp", "1" if value else "0")
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
from modules.nfregex.nftables import FiregexTables
|
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
|
from modules.nfregex.models import Service, Regex
|
||||||
import re, os, asyncio
|
import re
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
|
from utils import DEBUG
|
||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
nft = FiregexTables()
|
nft = FiregexTables()
|
||||||
|
|
||||||
@@ -10,7 +14,6 @@ class RegexFilter:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, regex,
|
self, regex,
|
||||||
is_case_sensitive=True,
|
is_case_sensitive=True,
|
||||||
is_blacklist=True,
|
|
||||||
input_mode=False,
|
input_mode=False,
|
||||||
output_mode=False,
|
output_mode=False,
|
||||||
blocked_packets=0,
|
blocked_packets=0,
|
||||||
@@ -19,8 +22,8 @@ class RegexFilter:
|
|||||||
):
|
):
|
||||||
self.regex = regex
|
self.regex = regex
|
||||||
self.is_case_sensitive = is_case_sensitive
|
self.is_case_sensitive = is_case_sensitive
|
||||||
self.is_blacklist = is_blacklist
|
if input_mode == output_mode:
|
||||||
if input_mode == output_mode: input_mode = output_mode = True # (False, False) == (True, True)
|
input_mode = output_mode = True # (False, False) == (True, True)
|
||||||
self.input_mode = input_mode
|
self.input_mode = input_mode
|
||||||
self.output_mode = output_mode
|
self.output_mode = output_mode
|
||||||
self.blocked = blocked_packets
|
self.blocked = blocked_packets
|
||||||
@@ -32,19 +35,21 @@ class RegexFilter:
|
|||||||
def from_regex(cls, regex:Regex, update_func = None):
|
def from_regex(cls, regex:Regex, update_func = None):
|
||||||
return cls(
|
return cls(
|
||||||
id=regex.id, regex=regex.regex, is_case_sensitive=regex.is_case_sensitive,
|
id=regex.id, regex=regex.regex, is_case_sensitive=regex.is_case_sensitive,
|
||||||
is_blacklist=regex.is_blacklist, blocked_packets=regex.blocked_packets,
|
blocked_packets=regex.blocked_packets,
|
||||||
input_mode = regex.mode in ["C","B"], output_mode=regex.mode in ["S","B"],
|
input_mode = regex.mode in ["C","B"], output_mode=regex.mode in ["S","B"],
|
||||||
update_func = update_func
|
update_func = update_func
|
||||||
)
|
)
|
||||||
def compile(self):
|
def compile(self):
|
||||||
if isinstance(self.regex, str): self.regex = self.regex.encode()
|
if isinstance(self.regex, str):
|
||||||
if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether")
|
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!
|
re.compile(self.regex) # raise re.error if it's invalid!
|
||||||
case_sensitive = "1" if self.is_case_sensitive else "0"
|
case_sensitive = "1" if self.is_case_sensitive else "0"
|
||||||
if self.input_mode:
|
if self.input_mode:
|
||||||
yield case_sensitive + "C" + self.regex.hex() if self.is_blacklist else case_sensitive + "c"+ self.regex.hex()
|
yield case_sensitive + "C" + self.regex.hex()
|
||||||
if self.output_mode:
|
if self.output_mode:
|
||||||
yield case_sensitive + "S" + self.regex.hex() if self.is_blacklist else case_sensitive + "s"+ self.regex.hex()
|
yield case_sensitive + "S" + self.regex.hex()
|
||||||
|
|
||||||
async def update(self):
|
async def update(self):
|
||||||
if self.update_func:
|
if self.update_func:
|
||||||
@@ -60,6 +65,10 @@ class FiregexInterceptor:
|
|||||||
self.update_config_lock:asyncio.Lock
|
self.update_config_lock:asyncio.Lock
|
||||||
self.process:asyncio.subprocess.Process
|
self.process:asyncio.subprocess.Process
|
||||||
self.update_task: asyncio.Task
|
self.update_task: asyncio.Task
|
||||||
|
self.ack_arrived = False
|
||||||
|
self.ack_status = None
|
||||||
|
self.ack_fail_what = ""
|
||||||
|
self.ack_lock = asyncio.Lock()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def start(cls, srv: Service):
|
async def start(cls, srv: Service):
|
||||||
@@ -67,16 +76,19 @@ class FiregexInterceptor:
|
|||||||
self.srv = srv
|
self.srv = srv
|
||||||
self.filter_map_lock = asyncio.Lock()
|
self.filter_map_lock = asyncio.Lock()
|
||||||
self.update_config_lock = asyncio.Lock()
|
self.update_config_lock = asyncio.Lock()
|
||||||
input_range, output_range = await self._start_binary()
|
queue_range = await self._start_binary()
|
||||||
self.update_task = asyncio.create_task(self.update_blocked())
|
self.update_task = asyncio.create_task(self.update_blocked())
|
||||||
nft.add(self.srv, input_range, output_range)
|
nft.add(self.srv, queue_range)
|
||||||
|
if not self.ack_lock.locked():
|
||||||
|
await self.ack_lock.acquire()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def _start_binary(self):
|
async def _start_binary(self):
|
||||||
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cppqueue")
|
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cppqueue")
|
||||||
self.process = await asyncio.create_subprocess_exec(
|
self.process = await asyncio.create_subprocess_exec(
|
||||||
proxy_binary_path,
|
proxy_binary_path,
|
||||||
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
|
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE,
|
||||||
|
env={"MATCH_MODE": "stream" if self.srv.proto == "tcp" else "block", "NTHREADS": os.getenv("NTHREADS","1")},
|
||||||
)
|
)
|
||||||
line_fut = self.process.stdout.readuntil()
|
line_fut = self.process.stdout.readuntil()
|
||||||
try:
|
try:
|
||||||
@@ -87,7 +99,7 @@ class FiregexInterceptor:
|
|||||||
line = line_fut.decode()
|
line = line_fut.decode()
|
||||||
if line.startswith("QUEUES "):
|
if line.startswith("QUEUES "):
|
||||||
params = line.split()
|
params = line.split()
|
||||||
return (int(params[2]), int(params[3])), (int(params[5]), int(params[6]))
|
return (int(params[1]), int(params[2]))
|
||||||
else:
|
else:
|
||||||
self.process.kill()
|
self.process.kill()
|
||||||
raise Exception("Invalid binary output")
|
raise Exception("Invalid binary output")
|
||||||
@@ -96,14 +108,24 @@ class FiregexInterceptor:
|
|||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
line = (await self.process.stdout.readuntil()).decode()
|
line = (await self.process.stdout.readuntil()).decode()
|
||||||
if line.startswith("BLOCKED"):
|
if DEBUG:
|
||||||
|
print(line)
|
||||||
|
if line.startswith("BLOCKED "):
|
||||||
regex_id = line.split()[1]
|
regex_id = line.split()[1]
|
||||||
async with self.filter_map_lock:
|
async with self.filter_map_lock:
|
||||||
if regex_id in self.filter_map:
|
if regex_id in self.filter_map:
|
||||||
self.filter_map[regex_id].blocked+=1
|
self.filter_map[regex_id].blocked+=1
|
||||||
await self.filter_map[regex_id].update()
|
await self.filter_map[regex_id].update()
|
||||||
except asyncio.CancelledError: pass
|
if line.startswith("ACK "):
|
||||||
except asyncio.IncompleteReadError: pass
|
self.ack_arrived = True
|
||||||
|
self.ack_status = line.split()[1].upper() == "OK"
|
||||||
|
if not self.ack_status:
|
||||||
|
self.ack_fail_what = " ".join(line.split()[2:])
|
||||||
|
self.ack_lock.release()
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
except asyncio.IncompleteReadError:
|
||||||
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@@ -116,6 +138,14 @@ class FiregexInterceptor:
|
|||||||
async with self.update_config_lock:
|
async with self.update_config_lock:
|
||||||
self.process.stdin.write((" ".join(filters_codes)+"\n").encode())
|
self.process.stdin.write((" ".join(filters_codes)+"\n").encode())
|
||||||
await self.process.stdin.drain()
|
await self.process.stdin.drain()
|
||||||
|
try:
|
||||||
|
async with asyncio.timeout(3):
|
||||||
|
await self.ack_lock.acquire()
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
if not self.ack_arrived or not self.ack_status:
|
||||||
|
raise HTTPException(status_code=500, detail=f"NFQ error: {self.ack_fail_what}")
|
||||||
|
|
||||||
|
|
||||||
async def reload(self, filters:list[RegexFilter]):
|
async def reload(self, filters:list[RegexFilter]):
|
||||||
async with self.filter_map_lock:
|
async with self.filter_map_lock:
|
||||||
@@ -135,6 +165,7 @@ class FiregexInterceptor:
|
|||||||
raw_filters = filter_obj.compile()
|
raw_filters = filter_obj.compile()
|
||||||
for filter in raw_filters:
|
for filter in raw_filters:
|
||||||
res[filter] = filter_obj
|
res[filter] = filter_obj
|
||||||
except Exception: pass
|
except Exception:
|
||||||
|
pass
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -30,14 +30,15 @@ class ServiceManager:
|
|||||||
new_filters = set([f.id for f in regexes])
|
new_filters = set([f.id for f in regexes])
|
||||||
#remove old filters
|
#remove old filters
|
||||||
for f in old_filters:
|
for f in old_filters:
|
||||||
if not f in new_filters:
|
if f not in new_filters:
|
||||||
del self.filters[f]
|
del self.filters[f]
|
||||||
#add new filters
|
#add new filters
|
||||||
for f in 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]
|
filter = [ele for ele in regexes if ele.id == f][0]
|
||||||
self.filters[f] = RegexFilter.from_regex(filter, self._stats_updater)
|
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):
|
def __update_status_db(self, status):
|
||||||
self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.srv.id)
|
self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.srv.id)
|
||||||
@@ -114,4 +115,5 @@ class FirewallManager:
|
|||||||
else:
|
else:
|
||||||
raise ServiceNotFoundException()
|
raise ServiceNotFoundException()
|
||||||
|
|
||||||
class ServiceNotFoundException(Exception): pass
|
class ServiceNotFoundException(Exception):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ class Service:
|
|||||||
|
|
||||||
|
|
||||||
class Regex:
|
class Regex:
|
||||||
def __init__(self, regex_id: int, regex: bytes, mode: str, service_id: str, is_blacklist: bool, blocked_packets: int, is_case_sensitive: bool, active: bool, **other):
|
def __init__(self, regex_id: int, regex: bytes, mode: str, service_id: str, blocked_packets: int, is_case_sensitive: bool, active: bool, **other):
|
||||||
self.regex = regex
|
self.regex = regex
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.service_id = service_id
|
self.service_id = service_id
|
||||||
self.is_blacklist = is_blacklist
|
|
||||||
self.blocked_packets = blocked_packets
|
self.blocked_packets = blocked_packets
|
||||||
self.id = regex_id
|
self.id = regex_id
|
||||||
self.is_case_sensitive = is_case_sensitive
|
self.is_case_sensitive = is_case_sensitive
|
||||||
|
|||||||
@@ -45,36 +45,37 @@ class FiregexTables(NFTableManager):
|
|||||||
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
|
{"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():
|
for ele in self.get():
|
||||||
if ele.__eq__(srv): return
|
if ele.__eq__(srv): return
|
||||||
|
|
||||||
init, end = queue_range_output
|
init, end = queue_range
|
||||||
if init > end: init, end = end, init
|
if init > end: init, end = end, init
|
||||||
self.cmd({ "insert":{ "rule": {
|
self.cmd(
|
||||||
|
{ "insert":{ "rule": {
|
||||||
"family": "inet",
|
"family": "inet",
|
||||||
"table": self.table_name,
|
"table": self.table_name,
|
||||||
"chain": self.output_chain,
|
"chain": self.output_chain,
|
||||||
"expr": [
|
"expr": [
|
||||||
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'saddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'saddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
||||||
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.port)}},
|
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.port)}},
|
||||||
|
{"mangle": {"key": {"meta": {"key": "mark"}},"value": 0x1338}},
|
||||||
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||||
]
|
]
|
||||||
}}})
|
}}},
|
||||||
|
{"insert":{"rule":{
|
||||||
init, end = queue_range_input
|
|
||||||
if init > end: init, end = end, init
|
|
||||||
self.cmd({"insert":{"rule":{
|
|
||||||
"family": "inet",
|
"family": "inet",
|
||||||
"table": self.table_name,
|
"table": self.table_name,
|
||||||
"chain": self.input_chain,
|
"chain": self.input_chain,
|
||||||
"expr": [
|
"expr": [
|
||||||
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'daddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'daddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
||||||
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "dport"}}, "op": "==", "right": int(srv.port)}},
|
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "dport"}}, "op": "==", "right": int(srv.port)}},
|
||||||
|
{"mangle": {"key": {"meta": {"key": "mark"}},"value": 0x1337}},
|
||||||
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||||
]
|
]
|
||||||
}}})
|
}}}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get(self) -> list[FiregexFilter]:
|
def get(self) -> list[FiregexFilter]:
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from utils.sqlite import SQLite
|
|||||||
|
|
||||||
nft = FiregexTables()
|
nft = FiregexTables()
|
||||||
|
|
||||||
class ServiceNotFoundException(Exception): pass
|
class ServiceNotFoundException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class ServiceManager:
|
class ServiceManager:
|
||||||
def __init__(self, srv: Service, db):
|
def __init__(self, srv: Service, db):
|
||||||
@@ -29,7 +30,8 @@ class ServiceManager:
|
|||||||
|
|
||||||
async def refresh(self, srv:Service):
|
async def refresh(self, srv:Service):
|
||||||
self.srv = srv
|
self.srv = srv
|
||||||
if self.active: await self.restart()
|
if self.active:
|
||||||
|
await self.restart()
|
||||||
|
|
||||||
def _set_status(self,active):
|
def _set_status(self,active):
|
||||||
self.active = active
|
self.active = active
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ class FiregexTables(NFTableManager):
|
|||||||
def add(self, srv:Service):
|
def add(self, srv:Service):
|
||||||
|
|
||||||
for ele in self.get():
|
for ele in self.get():
|
||||||
if ele.__eq__(srv): return
|
if ele.__eq__(srv):
|
||||||
|
return
|
||||||
|
|
||||||
self.cmd({ "insert":{ "rule": {
|
self.cmd({ "insert":{ "rule": {
|
||||||
"family": "inet",
|
"family": "inet",
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -5,7 +5,7 @@ from utils import ip_parse, ip_family, socketio_emit
|
|||||||
from utils.models import ResetRequest, StatusMessageModel
|
from utils.models import ResetRequest, StatusMessageModel
|
||||||
from modules.firewall.nftables import FiregexTables
|
from modules.firewall.nftables import FiregexTables
|
||||||
from modules.firewall.firewall import FirewallManager
|
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', {
|
db = SQLite('db/firewall-rules.db', {
|
||||||
'rules': {
|
'rules': {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class RegexModel(BaseModel):
|
|||||||
mode:str
|
mode:str
|
||||||
id:int
|
id:int
|
||||||
service_id:str
|
service_id:str
|
||||||
is_blacklist: bool
|
|
||||||
n_packets:int
|
n_packets:int
|
||||||
is_case_sensitive:bool
|
is_case_sensitive:bool
|
||||||
active:bool
|
active:bool
|
||||||
@@ -38,7 +37,6 @@ class RegexAddForm(BaseModel):
|
|||||||
regex: str
|
regex: str
|
||||||
mode: str
|
mode: str
|
||||||
active: bool|None = None
|
active: bool|None = None
|
||||||
is_blacklist: bool
|
|
||||||
is_case_sensitive: bool
|
is_case_sensitive: bool
|
||||||
|
|
||||||
class ServiceAddForm(BaseModel):
|
class ServiceAddForm(BaseModel):
|
||||||
@@ -66,7 +64,6 @@ db = SQLite('db/nft-regex.db', {
|
|||||||
'regex': 'TEXT NOT NULL',
|
'regex': 'TEXT NOT NULL',
|
||||||
'mode': 'VARCHAR(1) NOT NULL CHECK (mode IN ("C", "S", "B"))', # C = to the client, S = to the server, B = both
|
'mode': 'VARCHAR(1) NOT NULL CHECK (mode IN ("C", "S", "B"))', # C = to the client, S = to the server, B = both
|
||||||
'service_id': 'VARCHAR(100) 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',
|
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
|
||||||
'regex_id': 'INTEGER PRIMARY KEY',
|
'regex_id': 'INTEGER PRIMARY KEY',
|
||||||
'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))',
|
'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))',
|
||||||
@@ -75,7 +72,7 @@ db = SQLite('db/nft-regex.db', {
|
|||||||
},
|
},
|
||||||
'QUERY':[
|
'QUERY':[
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (port, ip_int, proto);",
|
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (port, ip_int, proto);",
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);"
|
"CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,mode,is_case_sensitive);"
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -92,12 +89,18 @@ async def reset(params: ResetRequest):
|
|||||||
db.init()
|
db.init()
|
||||||
else:
|
else:
|
||||||
db.restore()
|
db.restore()
|
||||||
|
try:
|
||||||
await firewall.init()
|
await firewall.init()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
async def startup():
|
async def startup():
|
||||||
db.init()
|
db.init()
|
||||||
|
try:
|
||||||
await firewall.init()
|
await firewall.init()
|
||||||
|
except Exception as e:
|
||||||
|
print("WARNING cannot start firewall:", e)
|
||||||
|
|
||||||
async def shutdown():
|
async def shutdown():
|
||||||
db.backup()
|
db.backup()
|
||||||
@@ -147,7 +150,8 @@ async def get_service_by_id(service_id: str):
|
|||||||
FROM services s LEFT JOIN regexes r ON s.service_id = r.service_id
|
FROM services s LEFT JOIN regexes r ON s.service_id = r.service_id
|
||||||
WHERE s.service_id = ? GROUP BY s.service_id;
|
WHERE s.service_id = ? GROUP BY s.service_id;
|
||||||
""", 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]
|
return res[0]
|
||||||
|
|
||||||
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
||||||
@@ -177,7 +181,8 @@ async def service_delete(service_id: str):
|
|||||||
async def service_rename(service_id: str, form: RenameForm):
|
async def service_rename(service_id: str, form: RenameForm):
|
||||||
"""Request to change the name of a specific service"""
|
"""Request to change the name of a specific service"""
|
||||||
form.name = refactor_name(form.name)
|
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:
|
try:
|
||||||
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
@@ -188,10 +193,11 @@ async def service_rename(service_id: str, form: RenameForm):
|
|||||||
@app.get('/service/{service_id}/regexes', response_model=list[RegexModel])
|
@app.get('/service/{service_id}/regexes', response_model=list[RegexModel])
|
||||||
async def get_service_regexe_list(service_id: str):
|
async def get_service_regexe_list(service_id: str):
|
||||||
"""Get the list of the regexes of a service"""
|
"""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("""
|
return db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
regex, mode, regex_id `id`, service_id, is_blacklist,
|
regex, mode, regex_id `id`, service_id,
|
||||||
blocked_packets n_packets, is_case_sensitive, active
|
blocked_packets n_packets, is_case_sensitive, active
|
||||||
FROM regexes WHERE service_id = ?;
|
FROM regexes WHERE service_id = ?;
|
||||||
""", service_id)
|
""", service_id)
|
||||||
@@ -201,11 +207,12 @@ async def get_regex_by_id(regex_id: int):
|
|||||||
"""Get regex info using his id"""
|
"""Get regex info using his id"""
|
||||||
res = db.query("""
|
res = db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
regex, mode, regex_id `id`, service_id, is_blacklist,
|
regex, mode, regex_id `id`, service_id,
|
||||||
blocked_packets n_packets, is_case_sensitive, active
|
blocked_packets n_packets, is_case_sensitive, active
|
||||||
FROM regexes WHERE `id` = ?;
|
FROM regexes WHERE `id` = ?;
|
||||||
""", regex_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]
|
return res[0]
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
||||||
@@ -247,8 +254,8 @@ async def add_new_regex(form: RegexAddForm):
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status_code=400, detail="Invalid regex")
|
raise HTTPException(status_code=400, detail="Invalid regex")
|
||||||
try:
|
try:
|
||||||
db.query("INSERT INTO regexes (service_id, regex, is_blacklist, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?, ?);",
|
db.query("INSERT INTO regexes (service_id, regex, 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 )
|
form.service_id, form.regex, form.mode, form.is_case_sensitive, True if form.active is None else form.active )
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
raise HTTPException(status_code=400, detail="An identical regex already exists")
|
raise HTTPException(status_code=400, detail="An identical regex already exists")
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ async def get_service_list():
|
|||||||
async def get_service_by_id(service_id: str):
|
async def get_service_by_id(service_id: str):
|
||||||
"""Get info about a specific service using his id"""
|
"""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)
|
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]
|
return res[0]
|
||||||
|
|
||||||
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
@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):
|
async def service_rename(service_id: str, form: RenameForm):
|
||||||
"""Request to change the name of a specific service"""
|
"""Request to change the name of a specific service"""
|
||||||
form.name = refactor_name(form.name)
|
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:
|
try:
|
||||||
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from ipaddress import ip_address, ip_interface
|
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_socketio import SocketManager
|
||||||
from fastapi import Path
|
from fastapi import Path
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
import json
|
|
||||||
|
|
||||||
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
|
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
|
||||||
|
|
||||||
@@ -16,7 +19,7 @@ ON_DOCKER = "DOCKER" in sys.argv
|
|||||||
DEBUG = "DEBUG" in sys.argv
|
DEBUG = "DEBUG" in sys.argv
|
||||||
FIREGEX_PORT = int(os.getenv("PORT","4444"))
|
FIREGEX_PORT = int(os.getenv("PORT","4444"))
|
||||||
JWT_ALGORITHM: str = "HS256"
|
JWT_ALGORITHM: str = "HS256"
|
||||||
API_VERSION = "2.2.0"
|
API_VERSION = "3.0.0"
|
||||||
|
|
||||||
PortType = Annotated[int, Path(gt=0, lt=65536)]
|
PortType = Annotated[int, Path(gt=0, lt=65536)]
|
||||||
|
|
||||||
@@ -31,7 +34,8 @@ async def socketio_emit(elements:list[str]):
|
|||||||
|
|
||||||
def refactor_name(name:str):
|
def refactor_name(name:str):
|
||||||
name = name.strip()
|
name = name.strip()
|
||||||
while " " in name: name = name.replace(" "," ")
|
while " " in name:
|
||||||
|
name = name.replace(" "," ")
|
||||||
return name
|
return name
|
||||||
|
|
||||||
class SysctlManager:
|
class SysctlManager:
|
||||||
@@ -125,8 +129,10 @@ class NFTableManager(Singleton):
|
|||||||
|
|
||||||
def cmd(self, *cmds):
|
def cmd(self, *cmds):
|
||||||
code, out, err = self.raw_cmd(*cmds)
|
code, out, err = self.raw_cmd(*cmds)
|
||||||
if code == 0: return out
|
if code == 0:
|
||||||
else: raise Exception(err)
|
return out
|
||||||
|
else:
|
||||||
|
raise Exception(err)
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
self.reset()
|
self.reset()
|
||||||
@@ -138,8 +144,10 @@ class NFTableManager(Singleton):
|
|||||||
|
|
||||||
def list_rules(self, tables = None, chains = None):
|
def list_rules(self, tables = None, chains = None):
|
||||||
for filter in [ele["rule"] for ele in self.raw_list() if "rule" in ele ]:
|
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 tables and filter["table"] not in tables:
|
||||||
if chains and filter["chain"] not in chains: continue
|
continue
|
||||||
|
if chains and filter["chain"] not in chains:
|
||||||
|
continue
|
||||||
yield filter
|
yield filter
|
||||||
|
|
||||||
def raw_list(self):
|
def raw_list(self):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
import os, httpx
|
import os
|
||||||
|
import httpx
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from starlette.responses import StreamingResponse
|
from starlette.responses import StreamingResponse
|
||||||
@@ -31,7 +32,8 @@ def frontend_deploy(app):
|
|||||||
return await frontend_debug_proxy(full_path)
|
return await frontend_debug_proxy(full_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
return {"details":"Frontend not started at "+f"http://127.0.0.1:{os.getenv('F_PORT','5173')}"}
|
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():
|
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")]
|
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:
|
if router.shutdown:
|
||||||
shutdowns.append(router.shutdown)
|
shutdowns.append(router.shutdown)
|
||||||
async def reset(reset_option:ResetRequest):
|
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():
|
async def startup():
|
||||||
for func in startups: await run_func(func)
|
for func in startups:
|
||||||
|
await run_func(func)
|
||||||
async def shutdown():
|
async def shutdown():
|
||||||
for func in shutdowns: await run_func(func)
|
for func in shutdowns:
|
||||||
|
await run_func(func)
|
||||||
return reset, startup, shutdown
|
return reset, startup, shutdown
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import json, sqlite3, os
|
import json
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
|
||||||
class SQLite():
|
class SQLite():
|
||||||
@@ -15,8 +17,10 @@ class SQLite():
|
|||||||
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
|
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
|
||||||
except Exception:
|
except Exception:
|
||||||
path_name = os.path.dirname(self.db_name)
|
path_name = os.path.dirname(self.db_name)
|
||||||
if not os.path.exists(path_name): os.makedirs(path_name)
|
if not os.path.exists(path_name):
|
||||||
with open(self.db_name, 'x'): pass
|
os.makedirs(path_name)
|
||||||
|
with open(self.db_name, 'x'):
|
||||||
|
pass
|
||||||
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
|
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
|
||||||
def dict_factory(cursor, row):
|
def dict_factory(cursor, row):
|
||||||
d = {}
|
d = {}
|
||||||
@@ -36,13 +40,15 @@ class SQLite():
|
|||||||
with open(self.db_name, "wb") as f:
|
with open(self.db_name, "wb") as f:
|
||||||
f.write(self.__backup)
|
f.write(self.__backup)
|
||||||
self.__backup = None
|
self.__backup = None
|
||||||
if were_active: self.connect()
|
if were_active:
|
||||||
|
self.connect()
|
||||||
|
|
||||||
def delete_backup(self):
|
def delete_backup(self):
|
||||||
self.__backup = None
|
self.__backup = None
|
||||||
|
|
||||||
def disconnect(self) -> None:
|
def disconnect(self) -> None:
|
||||||
if self.conn: self.conn.close()
|
if self.conn:
|
||||||
|
self.conn.close()
|
||||||
self.conn = None
|
self.conn = None
|
||||||
|
|
||||||
def create_schema(self, tables = {}) -> None:
|
def create_schema(self, tables = {}) -> None:
|
||||||
@@ -50,9 +56,11 @@ class SQLite():
|
|||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
cur.execute("CREATE TABLE IF NOT EXISTS main.keys_values(key VARCHAR(100) PRIMARY KEY, value VARCHAR(100) NOT NULL);")
|
cur.execute("CREATE TABLE IF NOT EXISTS main.keys_values(key VARCHAR(100) PRIMARY KEY, value VARCHAR(100) NOT NULL);")
|
||||||
for t in tables:
|
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]))
|
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()
|
cur.close()
|
||||||
|
|
||||||
def query(self, query, *values):
|
def query(self, query, *values):
|
||||||
@@ -82,8 +90,10 @@ class SQLite():
|
|||||||
raise e
|
raise e
|
||||||
finally:
|
finally:
|
||||||
cur.close()
|
cur.close()
|
||||||
try: self.conn.commit()
|
try:
|
||||||
except Exception: pass
|
self.conn.commit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
@@ -92,7 +102,8 @@ class SQLite():
|
|||||||
def init(self):
|
def init(self):
|
||||||
self.connect()
|
self.connect()
|
||||||
try:
|
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:
|
except Exception:
|
||||||
self.delete()
|
self.delete()
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 116 KiB |
484
frontend/bun.lock
Normal file
484
frontend/bun.lock
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "firegex-frontend",
|
||||||
|
"dependencies": {
|
||||||
|
"@hello-pangea/dnd": "^16.6.0",
|
||||||
|
"@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.17.16",
|
||||||
|
"@types/react": "^18.3.18",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
|
"react-router-dom": "^7.1.5",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"web-vitals": "^2.1.4",
|
||||||
|
"zustand": "^5.0.3",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tanstack/react-query-devtools": "^4.36.1",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"vite": "^4.5.9",
|
||||||
|
"vite-plugin-svgr": "^3.3.0",
|
||||||
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||||
|
|
||||||
|
"@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||||
|
|
||||||
|
"@babel/compat-data": ["@babel/compat-data@7.26.5", "", {}, "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg=="],
|
||||||
|
|
||||||
|
"@babel/core": ["@babel/core@7.26.7", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.5", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.7", "@babel/parser": "^7.26.7", "@babel/template": "^7.25.9", "@babel/traverse": "^7.26.7", "@babel/types": "^7.26.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA=="],
|
||||||
|
|
||||||
|
"@babel/generator": ["@babel/generator@7.26.5", "", { "dependencies": { "@babel/parser": "^7.26.5", "@babel/types": "^7.26.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw=="],
|
||||||
|
|
||||||
|
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.26.5", "", { "dependencies": { "@babel/compat-data": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="],
|
||||||
|
|
||||||
|
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="],
|
||||||
|
|
||||||
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.25.9", "", {}, "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="],
|
||||||
|
|
||||||
|
"@babel/helpers": ["@babel/helpers@7.26.7", "", { "dependencies": { "@babel/template": "^7.25.9", "@babel/types": "^7.26.7" } }, "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A=="],
|
||||||
|
|
||||||
|
"@babel/parser": ["@babel/parser@7.26.7", "", { "dependencies": { "@babel/types": "^7.26.7" }, "bin": "./bin/babel-parser.js" }, "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg=="],
|
||||||
|
|
||||||
|
"@babel/runtime": ["@babel/runtime@7.26.7", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ=="],
|
||||||
|
|
||||||
|
"@babel/template": ["@babel/template@7.25.9", "", { "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg=="],
|
||||||
|
|
||||||
|
"@babel/traverse": ["@babel/traverse@7.26.7", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.5", "@babel/parser": "^7.26.7", "@babel/template": "^7.25.9", "@babel/types": "^7.26.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA=="],
|
||||||
|
|
||||||
|
"@babel/types": ["@babel/types@7.26.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||||
|
|
||||||
|
"@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
|
||||||
|
|
||||||
|
"@floating-ui/dom": ["@floating-ui/dom@1.6.13", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w=="],
|
||||||
|
|
||||||
|
"@floating-ui/react": ["@floating-ui/react@0.26.28", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw=="],
|
||||||
|
|
||||||
|
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.2", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A=="],
|
||||||
|
|
||||||
|
"@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
|
||||||
|
|
||||||
|
"@hello-pangea/dnd": ["@hello-pangea/dnd@16.6.0", "", { "dependencies": { "@babel/runtime": "^7.24.1", "css-box-model": "^1.2.1", "memoize-one": "^6.0.0", "raf-schd": "^4.0.3", "react-redux": "^8.1.3", "redux": "^4.2.1", "use-memo-one": "^1.1.3" }, "peerDependencies": { "react": "^16.8.5 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" } }, "sha512-vfZ4GydqbtUPXSLfAvKvXQ6xwRzIjUSjVU0Sx+70VOhc2xx6CdmJXJ8YhH70RpbTUGjxctslQTHul9sIOxCfFQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
|
||||||
|
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||||
|
|
||||||
|
"@mantine/core": ["@mantine/core@7.16.2", "", { "dependencies": { "@floating-ui/react": "^0.26.28", "clsx": "^2.1.1", "react-number-format": "^5.4.3", "react-remove-scroll": "^2.6.2", "react-textarea-autosize": "8.5.6", "type-fest": "^4.27.0" }, "peerDependencies": { "@mantine/hooks": "7.16.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-6dwFz+8HrOqFan7GezgpoWyZSCxedh10S8iILGVsc3GXiD4gzo+3VZndZKccktkYZ3GVC9E3cCS3SxbiyKSAVw=="],
|
||||||
|
|
||||||
|
"@mantine/form": ["@mantine/form@7.16.2", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "klona": "^2.0.6" }, "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-JZkLbZ7xWAZndPrxObkf10gjHj57x8yvI/vobjDhfWN3zFPTSWmSSF6yBE1FpITseOs3oR03hlkqG6EclK6g+g=="],
|
||||||
|
|
||||||
|
"@mantine/hooks": ["@mantine/hooks@7.16.2", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-ZFHQhDi9T+r6VR5NEeE47gigPPIAHVIKDOCWsCsbCqHc3yz5l8kiO2RdfUmsTKV2KD/AiXnAw4b6pjQEP58GOg=="],
|
||||||
|
|
||||||
|
"@mantine/modals": ["@mantine/modals@7.16.2", "", { "peerDependencies": { "@mantine/core": "7.16.2", "@mantine/hooks": "7.16.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-REwAV53Fcz021EE3zLyYdkdFlfG+b24y279Y+eA1jCCH9VMLivXL+gacrox4BcpzREsic9nGVInSNv3VJwPlAQ=="],
|
||||||
|
|
||||||
|
"@mantine/notifications": ["@mantine/notifications@7.16.2", "", { "dependencies": { "@mantine/store": "7.16.2", "react-transition-group": "4.4.5" }, "peerDependencies": { "@mantine/core": "7.16.2", "@mantine/hooks": "7.16.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-U342XWiiRI1NvOlLsI6PH/pSNe0rxNClJ2w5orvjOMXvaAfDe52mhnzRmtzRxYENp06++3b/G7MjPH+466rF9Q=="],
|
||||||
|
|
||||||
|
"@mantine/store": ["@mantine/store@7.16.2", "", { "peerDependencies": { "react": "^18.x || ^19.x" } }, "sha512-9dEGLosrYSePlAwhfx3CxTLcWu2M98TtuYnelAiHEdNEkyafirvZxNt4paMoFXLKR1XPm5wdjDK7bdTaE0t7Og=="],
|
||||||
|
|
||||||
|
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
|
||||||
|
|
||||||
|
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-add-jsx-attribute": ["@svgr/babel-plugin-add-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-remove-jsx-attribute": ["@svgr/babel-plugin-remove-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-remove-jsx-empty-expression": ["@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-replace-jsx-attribute-value": ["@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-svg-dynamic-title": ["@svgr/babel-plugin-svg-dynamic-title@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-svg-em-dimensions": ["@svgr/babel-plugin-svg-em-dimensions@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-transform-react-native-svg": ["@svgr/babel-plugin-transform-react-native-svg@8.1.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q=="],
|
||||||
|
|
||||||
|
"@svgr/babel-plugin-transform-svg-component": ["@svgr/babel-plugin-transform-svg-component@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw=="],
|
||||||
|
|
||||||
|
"@svgr/babel-preset": ["@svgr/babel-preset@8.1.0", "", { "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", "@svgr/babel-plugin-transform-svg-component": "8.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug=="],
|
||||||
|
|
||||||
|
"@svgr/core": ["@svgr/core@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "camelcase": "^6.2.0", "cosmiconfig": "^8.1.3", "snake-case": "^3.0.4" } }, "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA=="],
|
||||||
|
|
||||||
|
"@svgr/hast-util-to-babel-ast": ["@svgr/hast-util-to-babel-ast@8.0.0", "", { "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" } }, "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q=="],
|
||||||
|
|
||||||
|
"@svgr/plugin-jsx": ["@svgr/plugin-jsx@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "@svgr/hast-util-to-babel-ast": "8.0.0", "svg-parser": "^2.0.4" }, "peerDependencies": { "@svgr/core": "*" } }, "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA=="],
|
||||||
|
|
||||||
|
"@tanstack/match-sorter-utils": ["@tanstack/match-sorter-utils@8.19.4", "", { "dependencies": { "remove-accents": "0.5.0" } }, "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg=="],
|
||||||
|
|
||||||
|
"@tanstack/query-core": ["@tanstack/query-core@4.36.1", "", {}, "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA=="],
|
||||||
|
|
||||||
|
"@tanstack/react-query": ["@tanstack/react-query@4.36.1", "", { "dependencies": { "@tanstack/query-core": "4.36.1", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-native": "*" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw=="],
|
||||||
|
|
||||||
|
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@4.36.1", "", { "dependencies": { "@tanstack/match-sorter-utils": "^8.7.0", "superjson": "^1.10.0", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "@tanstack/react-query": "^4.36.1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-WYku83CKP3OevnYSG8Y/QO9g0rT75v1om5IvcWUwiUZJ4LanYGLVCZ8TdFG5jfsq4Ej/lu2wwDAULEUnRIMBSw=="],
|
||||||
|
|
||||||
|
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||||
|
|
||||||
|
"@types/babel__generator": ["@types/babel__generator@7.6.8", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw=="],
|
||||||
|
|
||||||
|
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||||
|
|
||||||
|
"@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="],
|
||||||
|
|
||||||
|
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
||||||
|
|
||||||
|
"@types/hoist-non-react-statics": ["@types/hoist-non-react-statics@3.3.6", "", { "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" } }, "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw=="],
|
||||||
|
|
||||||
|
"@types/jest": ["@types/jest@27.5.2", "", { "dependencies": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" } }, "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@20.17.16", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw=="],
|
||||||
|
|
||||||
|
"@types/prop-types": ["@types/prop-types@15.7.14", "", {}, "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@18.3.18", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@18.3.5", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q=="],
|
||||||
|
|
||||||
|
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.3", "", {}, "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="],
|
||||||
|
|
||||||
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.3.4", "", { "dependencies": { "@babel/core": "^7.26.0", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||||
|
|
||||||
|
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
|
|
||||||
|
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
|
|
||||||
|
"browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="],
|
||||||
|
|
||||||
|
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||||
|
|
||||||
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
||||||
|
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||||
|
|
||||||
|
"caniuse-lite": ["caniuse-lite@1.0.30001696", "", {}, "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ=="],
|
||||||
|
|
||||||
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
|
|
||||||
|
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||||
|
|
||||||
|
"copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="],
|
||||||
|
|
||||||
|
"cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="],
|
||||||
|
|
||||||
|
"css-box-model": ["css-box-model@1.2.1", "", { "dependencies": { "tiny-invariant": "^1.0.6" } }, "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||||
|
|
||||||
|
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||||
|
|
||||||
|
"diff-sequences": ["diff-sequences@27.5.1", "", {}, "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ=="],
|
||||||
|
|
||||||
|
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||||
|
|
||||||
|
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
||||||
|
|
||||||
|
"electron-to-chromium": ["electron-to-chromium@1.5.90", "", {}, "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug=="],
|
||||||
|
|
||||||
|
"engine.io-client": ["engine.io-client@6.6.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w=="],
|
||||||
|
|
||||||
|
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
||||||
|
|
||||||
|
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
|
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
|
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
|
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||||
|
|
||||||
|
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||||
|
|
||||||
|
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||||
|
|
||||||
|
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||||
|
|
||||||
|
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
||||||
|
|
||||||
|
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
|
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||||
|
|
||||||
|
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||||
|
|
||||||
|
"is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="],
|
||||||
|
|
||||||
|
"jest-diff": ["jest-diff@27.5.1", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^27.5.1", "jest-get-type": "^27.5.1", "pretty-format": "^27.5.1" } }, "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw=="],
|
||||||
|
|
||||||
|
"jest-get-type": ["jest-get-type@27.5.1", "", {}, "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw=="],
|
||||||
|
|
||||||
|
"jest-matcher-utils": ["jest-matcher-utils@27.5.1", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^27.5.1", "jest-get-type": "^27.5.1", "pretty-format": "^27.5.1" } }, "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw=="],
|
||||||
|
|
||||||
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
|
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||||
|
|
||||||
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
|
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||||
|
|
||||||
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
|
"klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
|
||||||
|
|
||||||
|
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||||
|
|
||||||
|
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||||
|
|
||||||
|
"lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
|
||||||
|
|
||||||
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
|
"memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||||
|
|
||||||
|
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
|
||||||
|
|
||||||
|
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
||||||
|
|
||||||
|
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
|
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||||
|
|
||||||
|
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||||
|
|
||||||
|
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="],
|
||||||
|
|
||||||
|
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||||
|
|
||||||
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
|
|
||||||
|
"raf-schd": ["raf-schd@4.0.3", "", {}, "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="],
|
||||||
|
|
||||||
|
"react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
|
||||||
|
|
||||||
|
"react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
|
||||||
|
|
||||||
|
"react-icons": ["react-icons@5.4.0", "", { "peerDependencies": { "react": "*" } }, "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ=="],
|
||||||
|
|
||||||
|
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||||
|
|
||||||
|
"react-number-format": ["react-number-format@5.4.3", "", { "peerDependencies": { "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ=="],
|
||||||
|
|
||||||
|
"react-redux": ["react-redux@8.1.3", "", { "dependencies": { "@babel/runtime": "^7.12.1", "@types/hoist-non-react-statics": "^3.3.1", "@types/use-sync-external-store": "^0.0.3", "hoist-non-react-statics": "^3.3.2", "react-is": "^18.0.0", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { "@types/react": "^16.8 || ^17.0 || ^18.0", "@types/react-dom": "^16.8 || ^17.0 || ^18.0", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0", "react-native": ">=0.59", "redux": "^4 || ^5.0.0-beta.0" }, "optionalPeers": ["@types/react", "@types/react-dom", "react-dom", "react-native", "redux"] }, "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw=="],
|
||||||
|
|
||||||
|
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
|
||||||
|
|
||||||
|
"react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
|
||||||
|
|
||||||
|
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
||||||
|
|
||||||
|
"react-router": ["react-router@7.1.5", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0", "turbo-stream": "2.4.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA=="],
|
||||||
|
|
||||||
|
"react-router-dom": ["react-router-dom@7.1.5", "", { "dependencies": { "react-router": "7.1.5" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-/4f9+up0Qv92D3bB8iN5P1s3oHAepSGa9h5k6tpTFlixTTskJZwKGhJ6vRJ277tLD1zuaZTt95hyGWV1Z37csQ=="],
|
||||||
|
|
||||||
|
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
||||||
|
|
||||||
|
"react-textarea-autosize": ["react-textarea-autosize@8.5.6", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aT3ioKXMa8f6zHYGebhbdMD2L00tKeRX1zuVuDx9YQK/JLLRSaSxq3ugECEmUB9z2kvk6bFSIoRHLkkUv0RJiw=="],
|
||||||
|
|
||||||
|
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
|
||||||
|
|
||||||
|
"redux": ["redux@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w=="],
|
||||||
|
|
||||||
|
"regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
|
||||||
|
|
||||||
|
"remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="],
|
||||||
|
|
||||||
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
|
|
||||||
|
"rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="],
|
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
|
||||||
|
|
||||||
|
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||||
|
|
||||||
|
"snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="],
|
||||||
|
|
||||||
|
"socket.io-client": ["socket.io-client@4.8.1", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ=="],
|
||||||
|
|
||||||
|
"socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"superjson": ["superjson@1.13.3", "", { "dependencies": { "copy-anything": "^3.0.2" } }, "sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg=="],
|
||||||
|
|
||||||
|
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||||
|
|
||||||
|
"svg-parser": ["svg-parser@2.0.4", "", {}, "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="],
|
||||||
|
|
||||||
|
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
|
||||||
|
|
||||||
|
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||||
|
|
||||||
|
"tsconfck": ["tsconfck@3.1.4", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"turbo-stream": ["turbo-stream@2.4.0", "", {}, "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="],
|
||||||
|
|
||||||
|
"type-fest": ["type-fest@4.33.0", "", {}, "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
|
||||||
|
|
||||||
|
"update-browserslist-db": ["update-browserslist-db@1.1.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg=="],
|
||||||
|
|
||||||
|
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
||||||
|
|
||||||
|
"use-composed-ref": ["use-composed-ref@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w=="],
|
||||||
|
|
||||||
|
"use-isomorphic-layout-effect": ["use-isomorphic-layout-effect@1.2.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w=="],
|
||||||
|
|
||||||
|
"use-latest": ["use-latest@1.3.0", "", { "dependencies": { "use-isomorphic-layout-effect": "^1.1.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ=="],
|
||||||
|
|
||||||
|
"use-memo-one": ["use-memo-one@1.1.3", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ=="],
|
||||||
|
|
||||||
|
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
||||||
|
|
||||||
|
"use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="],
|
||||||
|
|
||||||
|
"vite": ["vite@4.5.9", "", { "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", "rollup": "^3.27.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw=="],
|
||||||
|
|
||||||
|
"vite-plugin-svgr": ["vite-plugin-svgr@3.3.0", "", { "dependencies": { "@rollup/pluginutils": "^5.0.4", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0" }, "peerDependencies": { "vite": "^2.6.0 || 3 || 4" } }, "sha512-vWZMCcGNdPqgziYFKQ3Y95XP0d0YGp28+MM3Dp9cTa/px5CKcHHrIoPl2Jw81rgVm6/ZUNONzjXbZQZ7Kw66og=="],
|
||||||
|
|
||||||
|
"vite-tsconfig-paths": ["vite-tsconfig-paths@4.3.2", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA=="],
|
||||||
|
|
||||||
|
"web-vitals": ["web-vitals@2.1.4", "", {}, "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg=="],
|
||||||
|
|
||||||
|
"ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
|
||||||
|
|
||||||
|
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
|
||||||
|
|
||||||
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
|
"zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="],
|
||||||
|
|
||||||
|
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
|
"engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||||
|
|
||||||
|
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
|
"pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||||
|
|
||||||
|
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
|
"socket.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||||
|
|
||||||
|
"socket.io-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -5,25 +5,25 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hello-pangea/dnd": "^16.6.0",
|
"@hello-pangea/dnd": "^16.6.0",
|
||||||
"@mantine/core": "^7.13.2",
|
"@mantine/core": "^7.16.2",
|
||||||
"@mantine/form": "^7.13.2",
|
"@mantine/form": "^7.16.2",
|
||||||
"@mantine/hooks": "^7.13.2",
|
"@mantine/hooks": "^7.16.2",
|
||||||
"@mantine/modals": "^7.13.2",
|
"@mantine/modals": "^7.16.2",
|
||||||
"@mantine/notifications": "^7.13.2",
|
"@mantine/notifications": "^7.16.2",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^4.36.1",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/node": "^20.16.11",
|
"@types/node": "^20.17.16",
|
||||||
"@types/react": "^18.3.11",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.5",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.0.0",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-router-dom": "^6.27.0",
|
"react-router-dom": "^7.1.5",
|
||||||
"socket.io-client": "^4.8.0",
|
"socket.io-client": "^4.8.1",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^5.7.3",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"zustand": "^5.0.0-rc.2"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/react-query-devtools": "^4.36.1",
|
"@tanstack/react-query-devtools": "^4.36.1",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"vite": "^4.5.5",
|
"vite": "^4.5.9",
|
||||||
"vite-plugin-svgr": "^3.3.0",
|
"vite-plugin-svgr": "^3.3.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2"
|
"vite-tsconfig-paths": "^4.3.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { DEV_IP_BACKEND, errorNotify, getstatus, HomeRedirector, IS_DEV, login, setpassword } from './js/utils';
|
||||||
import NFRegex from './pages/NFRegex';
|
import NFRegex from './pages/NFRegex';
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import RegexProxy from './pages/RegexProxy';
|
|
||||||
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
|
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
|
||||||
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
|
|
||||||
import PortHijack from './pages/PortHijack';
|
import PortHijack from './pages/PortHijack';
|
||||||
import { Firewall } from './pages/Firewall';
|
import { Firewall } from './pages/Firewall';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
@@ -150,9 +148,6 @@ function App() {
|
|||||||
<Route path="nfregex" element={<NFRegex><Outlet /></NFRegex>} >
|
<Route path="nfregex" element={<NFRegex><Outlet /></NFRegex>} >
|
||||||
<Route path=":srv" element={<ServiceDetailsNFRegex />} />
|
<Route path=":srv" element={<ServiceDetailsNFRegex />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
|
|
||||||
<Route path=":srv" element={<ServiceDetailsProxyRegex />} />
|
|
||||||
</Route>
|
|
||||||
<Route path="firewall" element={<Firewall />} />
|
<Route path="firewall" element={<Firewall />} />
|
||||||
<Route path="porthijack" element={<PortHijack />} />
|
<Route path="porthijack" element={<PortHijack />} />
|
||||||
<Route path="*" element={<HomeRedirector />} />
|
<Route path="*" element={<HomeRedirector />} />
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { Button, Group, Space, TextInput, Notification, Switch, NativeSelect, Modal, Alert } from '@mantine/core';
|
import { Button, Group, Space, TextInput, Notification, Switch, Modal, Select } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { RegexAddForm } from '../js/models';
|
import { RegexAddForm } from '../js/models';
|
||||||
import { b64decode, b64encode, getapiobject, okNotify } from '../js/utils';
|
import { b64decode, b64encode, getapiobject, okNotify } from '../js/utils';
|
||||||
import { ImCross } from "react-icons/im"
|
import { ImCross } from "react-icons/im"
|
||||||
import FilterTypeSelector from './FilterTypeSelector';
|
|
||||||
import { AiFillWarning } from 'react-icons/ai';
|
|
||||||
|
|
||||||
type RegexAddInfo = {
|
type RegexAddInfo = {
|
||||||
regex:string,
|
regex:string,
|
||||||
type:string,
|
|
||||||
mode:string,
|
mode:string,
|
||||||
is_case_insensitive:boolean,
|
is_case_insensitive:boolean,
|
||||||
deactive:boolean
|
deactive:boolean
|
||||||
@@ -20,15 +17,13 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
|
|||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
regex:"",
|
regex:"",
|
||||||
type:"blacklist",
|
mode:"C",
|
||||||
mode:"C -> S",
|
|
||||||
is_case_insensitive:false,
|
is_case_insensitive:false,
|
||||||
deactive:false
|
deactive:false
|
||||||
},
|
},
|
||||||
validate:{
|
validate:{
|
||||||
regex: (value) => value !== "" ? null : "Regex is required",
|
regex: (value) => value !== "" ? null : "Regex is required",
|
||||||
type: (value) => ["blacklist","whitelist"].includes(value) ? null : "Invalid type",
|
mode: (value) => ['C', 'S', 'B'].includes(value) ? null : "Invalid mode",
|
||||||
mode: (value) => ['C -> S', 'S -> C', 'C <-> S'].includes(value) ? null : "Invalid mode",
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -43,13 +38,11 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
|
|||||||
|
|
||||||
const submitRequest = (values:RegexAddInfo) => {
|
const submitRequest = (values:RegexAddInfo) => {
|
||||||
setSubmitLoading(true)
|
setSubmitLoading(true)
|
||||||
const filter_mode = ({'C -> S':'C', 'S -> C':'S', 'C <-> S':'B'}[values.mode])
|
|
||||||
|
|
||||||
const request:RegexAddForm = {
|
const request:RegexAddForm = {
|
||||||
is_blacklist:values.type !== "whitelist",
|
|
||||||
is_case_sensitive: !values.is_case_insensitive,
|
is_case_sensitive: !values.is_case_insensitive,
|
||||||
service_id: service,
|
service_id: service,
|
||||||
mode: filter_mode?filter_mode:"B",
|
mode: values.mode?values.mode:"B",
|
||||||
regex: b64encode(values.regex),
|
regex: b64encode(values.regex),
|
||||||
active: !values.deactive
|
active: !values.deactive
|
||||||
}
|
}
|
||||||
@@ -58,7 +51,7 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
|
|||||||
if (!res){
|
if (!res){
|
||||||
setSubmitLoading(false)
|
setSubmitLoading(false)
|
||||||
close();
|
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"){
|
}else if (res.toLowerCase() === "invalid regex"){
|
||||||
setSubmitLoading(false)
|
setSubmitLoading(false)
|
||||||
form.setFieldError("regex", "Invalid Regex")
|
form.setFieldError("regex", "Invalid Regex")
|
||||||
@@ -92,22 +85,16 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
|
|||||||
{...form.getInputProps('deactive', { type: 'checkbox' })}
|
{...form.getInputProps('deactive', { type: 'checkbox' })}
|
||||||
/>
|
/>
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
<NativeSelect
|
<Select
|
||||||
data={['C -> S', 'S -> C', 'C <-> S']}
|
data={[
|
||||||
|
{ value: 'C', label: 'Client -> Server' },
|
||||||
|
{ value: 'S', label: 'Server -> Client' },
|
||||||
|
{ value: 'B', label: 'Both (Client <-> Server)' },
|
||||||
|
]}
|
||||||
label="Choose the source of the packets to filter"
|
label="Choose the source of the packets to filter"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
{...form.getInputProps('mode')}
|
{...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">
|
<Group align="right" mt="md">
|
||||||
<Button loading={submitLoading} type="submit">Add Filter</Button>
|
<Button loading={submitLoading} type="submit">Add Filter</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
@@ -39,11 +39,6 @@ export default function NavBar() {
|
|||||||
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="lime" icon={<IoMdGitNetwork />} />
|
<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="firewall" closeNav={closeNav} name="Firewall Rules" color="red" icon={<PiWallLight />} />
|
||||||
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="blue" icon={<GrDirections />} />
|
<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>
|
</Box>
|
||||||
|
|
||||||
</AppShell.Navbar>
|
</AppShell.Navbar>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ServerResponse } from "../../js/models"
|
import { ServerResponse } from "../../js/models"
|
||||||
import { getapi, postapi } from "../../js/utils"
|
import { getapi, postapi } from "../../js/utils"
|
||||||
import { UseQueryOptions, useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
|
||||||
export type GeneralStats = {
|
export type GeneralStats = {
|
||||||
services:number
|
services:number
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import { RegexFilter } from '../../js/models';
|
|||||||
import { b64decode, errorNotify, getapiobject, okNotify } from '../../js/utils';
|
import { b64decode, errorNotify, getapiobject, okNotify } from '../../js/utils';
|
||||||
import { BsTrashFill } from "react-icons/bs"
|
import { BsTrashFill } from "react-icons/bs"
|
||||||
import YesNoModal from '../YesNoModal';
|
import YesNoModal from '../YesNoModal';
|
||||||
import FilterTypeSelector from '../FilterTypeSelector';
|
|
||||||
import { FaPause, FaPlay } from 'react-icons/fa';
|
import { FaPause, FaPlay } from 'react-icons/fa';
|
||||||
import { useClipboard } from '@mantine/hooks';
|
import { useClipboard } from '@mantine/hooks';
|
||||||
|
|
||||||
@@ -74,12 +73,6 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col className='center-flex' span={12}>
|
<Grid.Col className='center-flex' span={12}>
|
||||||
<Box className='center-flex-row'>
|
<Box className='center-flex-row'>
|
||||||
<FilterTypeSelector
|
|
||||||
size="md"
|
|
||||||
color="gray"
|
|
||||||
disabled
|
|
||||||
value={regexInfo.is_blacklist?"blacklist":"whitelist"}
|
|
||||||
/>
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
<Box className='center-flex'>
|
<Box className='center-flex'>
|
||||||
<Badge size="md" color="cyan" variant="filled">Service: {regexInfo.service_id}</Badge>
|
<Badge size="md" color="cyan" variant="filled">Service: {regexInfo.service_id}</Badge>
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export type RegexFilter = {
|
|||||||
id:number,
|
id:number,
|
||||||
service_id:string,
|
service_id:string,
|
||||||
regex:string
|
regex:string
|
||||||
is_blacklist:boolean,
|
|
||||||
is_case_sensitive:boolean,
|
is_case_sensitive:boolean,
|
||||||
mode:string //C S B => C->S S->C BOTH
|
mode:string //C S B => C->S S->C BOTH
|
||||||
n_packets:number,
|
n_packets:number,
|
||||||
@@ -47,7 +46,6 @@ export type RegexAddForm = {
|
|||||||
service_id:string,
|
service_id:string,
|
||||||
regex:string,
|
regex:string,
|
||||||
is_case_sensitive:boolean,
|
is_case_sensitive:boolean,
|
||||||
is_blacklist:boolean,
|
|
||||||
mode:string, // C->S S->C BOTH,
|
mode:string, // C->S S->C BOTH,
|
||||||
active: boolean
|
active: boolean
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ import { ImCross } from "react-icons/im";
|
|||||||
import { TiTick } from "react-icons/ti"
|
import { TiTick } from "react-icons/ti"
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import { nfregex } from "../components/NFRegex/utils";
|
import { nfregex } from "../components/NFRegex/utils";
|
||||||
import { regexproxy } from "../components/RegexProxy/utils";
|
|
||||||
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
|
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
|
||||||
import { Buffer } from "buffer"
|
import { Buffer } from "buffer"
|
||||||
import { QueryClient, useQuery } from "@tanstack/react-query";
|
import { QueryClient, useQuery } from "@tanstack/react-query";
|
||||||
@@ -17,7 +16,7 @@ export const regex_ipv4 = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.
|
|||||||
export const regex_ipv4_no_cidr = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
|
export const regex_ipv4_no_cidr = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
|
||||||
export const regex_port = "^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?$"
|
export const regex_port = "^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?$"
|
||||||
export const regex_range_port = "^(([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?)?)?$"
|
export const regex_range_port = "^(([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?)?)?$"
|
||||||
export const DEV_IP_BACKEND = "198.19.249.69:4444"
|
export const DEV_IP_BACKEND = "127.0.0.1:4444"
|
||||||
|
|
||||||
export const queryClient = new QueryClient({ defaultOptions: { queries: {
|
export const queryClient = new QueryClient({ defaultOptions: { queries: {
|
||||||
staleTime: Infinity
|
staleTime: Infinity
|
||||||
@@ -111,8 +110,6 @@ export function getapiobject(){
|
|||||||
switch(getMainPath()){
|
switch(getMainPath()){
|
||||||
case "nfregex":
|
case "nfregex":
|
||||||
return nfregex
|
return nfregex
|
||||||
case "regexproxy":
|
|
||||||
return regexproxy
|
|
||||||
}
|
}
|
||||||
throw new Error('No api for this tool!');
|
throw new Error('No api for this tool!');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function ServiceDetailsNFRegex() {
|
|||||||
const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false)
|
const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false)
|
||||||
const regexesList = nfregexServiceRegexesQuery(srv??"")
|
const regexesList = nfregexServiceRegexesQuery(srv??"")
|
||||||
|
|
||||||
|
if (services.isLoading) return <LoadingOverlay visible={true} />
|
||||||
if (!srv || !serviceInfo || regexesList.isError) return <Navigate to="/" replace />
|
if (!srv || !serviceInfo || regexesList.isError) return <Navigate to="/" replace />
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,60 +1,95 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from utils.colors import *
|
from utils.colors import colors, puts, sep
|
||||||
from utils.firegexapi import *
|
from utils.firegexapi import FiregexAPI
|
||||||
import argparse, secrets
|
import argparse
|
||||||
|
import secrets
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
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("--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("--password", "-p", type=str, required=True, help='Firegex password')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
sep()
|
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)
|
puts(f"{args.address}", color=colors.yellow)
|
||||||
|
|
||||||
firegex = FiregexAPI(args.address)
|
firegex = FiregexAPI(args.address)
|
||||||
|
|
||||||
#Connect to Firegex
|
#Connect to Firegex
|
||||||
if firegex.status()["status"] =="init":
|
if firegex.status()["status"] =="init":
|
||||||
if (firegex.set_password(args.password)): puts(f"Sucessfully set password to {args.password} ✔", color=colors.green)
|
if (firegex.set_password(args.password)):
|
||||||
else: puts(f"Test Failed: Unknown response or password already put ✗", color=colors.red); exit(1)
|
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:
|
else:
|
||||||
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
|
if (firegex.login(args.password)):
|
||||||
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
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)
|
if(firegex.status()["loggined"]):
|
||||||
else: puts(f"Test Failed: Unknown response or not logged in✗", color=colors.red); exit(1)
|
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
|
#Prepare second instance
|
||||||
firegex2 = FiregexAPI(args.address)
|
firegex2 = FiregexAPI(args.address)
|
||||||
if (firegex2.login(args.password)): puts(f"Sucessfully logged in on second instance ✔", color=colors.green)
|
if (firegex2.login(args.password)):
|
||||||
else: puts(f"Test Failed: Unknown response or wrong passowrd on second instance ✗", color=colors.red); exit(1)
|
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)
|
if(firegex2.status()["loggined"]):
|
||||||
else: puts(f"Test Failed: Unknown response or not logged in on second instance✗", color=colors.red); exit(1)
|
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
|
#Change password
|
||||||
new_password = secrets.token_hex(10)
|
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)
|
if (firegex.change_password(new_password,expire=True)):
|
||||||
else: puts(f"Test Failed: Coundl't change the password ✗", color=colors.red); exit(1)
|
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
|
#Check if we are still logged in
|
||||||
if(firegex.status()["loggined"]): puts(f"Correctly received status after password change ✔", color=colors.green)
|
if(firegex.status()["loggined"]):
|
||||||
else: puts(f"Test Failed: Unknown response or not logged after password change ✗", color=colors.red); exit(1)
|
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
|
#Check if second session expired and relog
|
||||||
|
|
||||||
if(not firegex2.status()["loggined"]): puts(f"Second instance was expired currectly ✔", color=colors.green)
|
if(not firegex2.status()["loggined"]):
|
||||||
else: puts(f"Test Failed: Still logged in on second instance, expire expected ✗", color=colors.red); exit(1)
|
puts("Second instance was expired currectly ✔", color=colors.green)
|
||||||
if (firegex2.login(new_password)): puts(f"Sucessfully logged in on second instance ✔", color=colors.green)
|
else:
|
||||||
else: puts(f"Test Failed: Unknown response or wrong passowrd on second instance ✗", color=colors.red); exit(1)
|
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
|
#Change it back
|
||||||
if (firegex.change_password(args.password,expire=False)): puts(f"Sucessfully restored the password ✔", color=colors.green)
|
if (firegex.change_password(args.password,expire=False)):
|
||||||
else: puts(f"Test Failed: Coundl't change the password ✗", color=colors.red); exit(1)
|
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
|
#Check if we are still logged in
|
||||||
if(firegex2.status()["loggined"]): puts(f"Correctly received status after password change ✔", color=colors.green)
|
if(firegex2.status()["loggined"]):
|
||||||
else: puts(f"Test Failed: Unknown response or not logged after password change ✗", color=colors.red); exit(1)
|
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)
|
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)
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from utils.colors import *
|
from utils.colors import colors, puts, sep
|
||||||
from utils.firegexapi import *
|
from utils.firegexapi import FiregexAPI
|
||||||
from utils.tcpserver import *
|
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
from time import sleep
|
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 = 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("--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("--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("--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("--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("--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("--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("--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("--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()
|
args = parser.parse_args()
|
||||||
sep()
|
sep()
|
||||||
@@ -28,22 +27,36 @@ puts(f"{args.address}", color=colors.yellow)
|
|||||||
firegex = FiregexAPI(args.address)
|
firegex = FiregexAPI(args.address)
|
||||||
|
|
||||||
#Connect to Firegex
|
#Connect to Firegex
|
||||||
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
|
if (firegex.login(args.password)):
|
||||||
else: puts(f"Benchmark Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
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
|
#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:
|
else:
|
||||||
service_id = firegex.px_add_service(args.service_name, args.port, internalPort=args.internal_port)
|
puts("Test Failed: Failed to create service ✗", color=colors.red)
|
||||||
if service_id: puts(f"Sucessfully created service {service_id} ✔", color=colors.green)
|
exit(1)
|
||||||
else: puts(f"Test Failed: Failed to create service ✗", color=colors.red); exit(1)
|
|
||||||
|
|
||||||
#Start iperf3
|
#Start iperf3
|
||||||
def startServer():
|
def startServer():
|
||||||
server = iperf3.Server()
|
server = iperf3.Server()
|
||||||
server.bind_address = '127.0.0.1'
|
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
|
server.verbose = False
|
||||||
while True:
|
while True:
|
||||||
server.run()
|
server.run()
|
||||||
@@ -63,27 +76,29 @@ sleep(1)
|
|||||||
|
|
||||||
|
|
||||||
#Get baseline reading
|
#Get baseline reading
|
||||||
puts(f"Baseline without proxy: ", color=colors.blue, end='')
|
puts("Baseline without proxy: ", color=colors.blue, end='')
|
||||||
print(f"{getReading(args.port if args.mode == 'netfilter' else args.internal_port)} MB/s")
|
print(f"{getReading(args.port)} MB/s")
|
||||||
|
|
||||||
#Start firewall
|
#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)
|
puts(f"Sucessfully started service with id {service_id} ✔", color=colors.green)
|
||||||
else:
|
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
|
#Get no regexs reading
|
||||||
results = []
|
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))
|
results.append(getReading(args.port))
|
||||||
print(f"{results[0]} MB/s")
|
print(f"{results[0]} MB/s")
|
||||||
|
|
||||||
#Add all the regexs
|
#Add all the regexs
|
||||||
for i in range(1,args.num_of_regexes+1):
|
for i in range(1,args.num_of_regexes+1):
|
||||||
regex = base64.b64encode(bytes(secrets.token_hex(16).encode())).decode()
|
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) ):
|
if not firegex.nf_add_regex(service_id,regex,"B",active=True,is_case_sensitive=False):
|
||||||
puts(f"Benchmark Failed: Coulnd't add the regex ✗", color=colors.red); exit_test(1)
|
puts("Benchmark Failed: Couldn't add the regex ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
puts(f"Performance with {i} regex(s): ", color=colors.red, end='')
|
puts(f"Performance with {i} regex(s): ", color=colors.red, end='')
|
||||||
results.append(getReading(args.port))
|
results.append(getReading(args.port))
|
||||||
print(f"{results[i]} MB/s")
|
print(f"{results[i]} MB/s")
|
||||||
@@ -96,9 +111,10 @@ with open(args.output_file,'w') as f:
|
|||||||
puts(f"Sucessfully written results to {args.output_file} ✔", color=colors.magenta)
|
puts(f"Sucessfully written results to {args.output_file} ✔", color=colors.magenta)
|
||||||
|
|
||||||
#Delete the Service
|
#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)
|
puts(f"Sucessfully delete service with id {service_id} ✔", color=colors.green)
|
||||||
else:
|
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()
|
server.terminate()
|
||||||
171
tests/nf_test.py
171
tests/nf_test.py
@@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from utils.colors import *
|
from utils.colors import colors, puts, sep
|
||||||
from utils.firegexapi import *
|
from utils.firegexapi import FiregexAPI
|
||||||
from utils.tcpserver import TcpServer
|
from utils.tcpserver import TcpServer
|
||||||
from utils.udpserver import UdpServer
|
from utils.udpserver import UdpServer
|
||||||
import argparse, secrets, base64,time
|
import argparse
|
||||||
|
import secrets
|
||||||
|
import base64
|
||||||
|
import time
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
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("--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()
|
args = parser.parse_args()
|
||||||
sep()
|
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)
|
puts(f"{args.address}", color=colors.yellow)
|
||||||
|
|
||||||
firegex = FiregexAPI(args.address)
|
firegex = FiregexAPI(args.address)
|
||||||
|
|
||||||
#Login
|
#Login
|
||||||
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
|
if (firegex.login(args.password)):
|
||||||
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
puts("Sucessfully logged in ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Unknown response or wrong passowrd ✗", color=colors.red)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
#Create server
|
#Create server
|
||||||
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port,ipv6=args.ipv6)
|
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port,ipv6=args.ipv6)
|
||||||
@@ -30,30 +36,47 @@ server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port,ipv6=args.i
|
|||||||
def exit_test(code):
|
def exit_test(code):
|
||||||
if service_id:
|
if service_id:
|
||||||
server.stop()
|
server.stop()
|
||||||
if(firegex.nf_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
|
if(firegex.nf_delete_service(service_id)):
|
||||||
else: puts(f"Test Failed: Coulnd't delete serivce ✗", color=colors.red); exit_test(1)
|
puts("Sucessfully deleted service ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Coulnd't delete serivce ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
exit(code)
|
exit(code)
|
||||||
|
|
||||||
service_id = firegex.nf_add_service(args.service_name, args.port, args.proto , "::1" if args.ipv6 else "127.0.0.1" )
|
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)
|
if service_id:
|
||||||
else: puts(f"Test Failed: Failed to create service ✗", color=colors.red); exit(1)
|
puts(f"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)
|
if(firegex.nf_start_service(service_id)):
|
||||||
else: puts(f"Test Failed: Failed to start service ✗", color=colors.red); exit_test(1)
|
puts("Sucessfully started service ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Failed to start service ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
|
||||||
server.start()
|
server.start()
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
if server.sendCheckData(secrets.token_bytes(432)):
|
try:
|
||||||
puts(f"Successfully tested first proxy with no regex ✔", color=colors.green)
|
if server.sendCheckData(secrets.token_bytes(432)):
|
||||||
else:
|
puts("Successfully tested first proxy with no regex ✔", color=colors.green)
|
||||||
puts(f"Test Failed: Data was corrupted ", color=colors.red); exit_test(1)
|
else:
|
||||||
|
puts("Test Failed: Data was corrupted ", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
except Exception as e:
|
||||||
|
puts("Test Failed: Couldn't send data to the server ", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
#Add new regex
|
#Add new regex
|
||||||
secret = bytes(secrets.token_hex(16).encode())
|
secret = bytes(secrets.token_hex(16).encode())
|
||||||
regex = base64.b64encode(secret).decode()
|
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_case_sensitive=True):
|
||||||
puts(f"Sucessfully added regex {str(secret)} ✔", color=colors.green)
|
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(f"Test Failed: Couldn't add the regex {str(secret)} ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
|
||||||
|
|
||||||
#Check if regex is present in the service
|
#Check if regex is present in the service
|
||||||
n_blocked = 0
|
n_blocked = 0
|
||||||
@@ -65,36 +88,58 @@ def checkRegex(regex, should_work=True, upper=False):
|
|||||||
if r["regex"] == regex:
|
if r["regex"] == regex:
|
||||||
#Test the regex
|
#Test the regex
|
||||||
s = base64.b64decode(regex).upper() if upper else base64.b64decode(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)):
|
if not server.sendCheckData(secrets.token_bytes(40) + s + secrets.token_bytes(40)):
|
||||||
puts(f"The malicious request was successfully blocked ✔", color=colors.green)
|
puts("The malicious request was successfully blocked ✔", color=colors.green)
|
||||||
n_blocked += 1
|
n_blocked += 1
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if firegex.nf_get_regex(r["id"])["n_packets"] == n_blocked:
|
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:
|
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:
|
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
|
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:
|
else:
|
||||||
if server.sendCheckData(secrets.token_bytes(200) + base64.b64decode(regex) + secrets.token_bytes(200)):
|
if server.sendCheckData(secrets.token_bytes(40) + base64.b64decode(regex) + secrets.token_bytes(40)):
|
||||||
puts(f"The request wasn't blocked ✔", color=colors.green)
|
puts("The request wasn't blocked ✔", color=colors.green)
|
||||||
else:
|
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)
|
||||||
|
|
||||||
|
def clear_regexes():
|
||||||
|
global n_blocked
|
||||||
|
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("Test Failed: Coulnd't delete the regex ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
break
|
||||||
|
|
||||||
checkRegex(regex)
|
checkRegex(regex)
|
||||||
|
|
||||||
#Pause the proxy
|
#Pause the proxy
|
||||||
if(firegex.nf_stop_service(service_id)): puts(f"Sucessfully paused service with id {service_id} ✔", color=colors.green)
|
if(firegex.nf_stop_service(service_id)):
|
||||||
else: puts(f"Test Failed: Coulnd't pause the service ✗", color=colors.red); exit_test(1)
|
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
|
#Check if it's actually paused
|
||||||
checkRegex(regex,should_work=False)
|
checkRegex(regex,should_work=False)
|
||||||
|
|
||||||
#Start firewall
|
#Start firewall
|
||||||
if(firegex.nf_start_service(service_id)): puts(f"Sucessfully started service with id {service_id} ✔", color=colors.green)
|
if(firegex.nf_start_service(service_id)):
|
||||||
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
|
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)
|
checkRegex(regex)
|
||||||
|
|
||||||
@@ -104,7 +149,8 @@ for r in firegex.nf_get_service_regexes(service_id):
|
|||||||
if(firegex.nf_disable_regex(r["id"])):
|
if(firegex.nf_disable_regex(r["id"])):
|
||||||
puts(f"Sucessfully disabled regex with id {r['id']} ✔", color=colors.green)
|
puts(f"Sucessfully disabled regex with id {r['id']} ✔", color=colors.green)
|
||||||
else:
|
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
|
break
|
||||||
|
|
||||||
#Check if it's actually disabled
|
#Check if it's actually disabled
|
||||||
@@ -116,69 +162,42 @@ for r in firegex.nf_get_service_regexes(service_id):
|
|||||||
if(firegex.nf_enable_regex(r["id"])):
|
if(firegex.nf_enable_regex(r["id"])):
|
||||||
puts(f"Sucessfully enabled regex with id {r['id']} ✔", color=colors.green)
|
puts(f"Sucessfully enabled regex with id {r['id']} ✔", color=colors.green)
|
||||||
else:
|
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
|
break
|
||||||
|
|
||||||
checkRegex(regex)
|
checkRegex(regex)
|
||||||
|
|
||||||
#Delete regex
|
#Delete regex
|
||||||
n_blocked = 0
|
clear_regexes()
|
||||||
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)
|
|
||||||
break
|
|
||||||
|
|
||||||
#Check if it's actually deleted
|
#Check if it's actually deleted
|
||||||
checkRegex(regex,should_work=False)
|
checkRegex(regex,should_work=False)
|
||||||
|
|
||||||
#Add case insensitive regex
|
#Add case insensitive regex
|
||||||
if(firegex.nf_add_regex(service_id,regex,"B",active=True,is_blacklist=True,is_case_sensitive=False)):
|
if(firegex.nf_add_regex(service_id,regex,"B",active=True, is_case_sensitive=False)):
|
||||||
puts(f"Sucessfully added case insensitive regex {str(secret)} ✔", color=colors.green)
|
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, upper=True)
|
||||||
checkRegex(regex)
|
checkRegex(regex)
|
||||||
|
|
||||||
#Delete regex
|
clear_regexes()
|
||||||
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)
|
|
||||||
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)
|
|
||||||
break
|
|
||||||
|
|
||||||
#Rename service
|
#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)
|
if(firegex.nf_rename_service(service_id,f"{args.service_name}2")):
|
||||||
else: puts(f"Test Failed: Coulnd't rename service ✗", color=colors.red); exit_test(1)
|
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
|
#Check if service was renamed correctly
|
||||||
for services in firegex.nf_get_services():
|
for services in firegex.nf_get_services():
|
||||||
if services["name"] == f"{args.service_name}2":
|
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)
|
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)
|
exit_test(1)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from utils.colors import *
|
from utils.colors import colors, puts, sep
|
||||||
from utils.firegexapi import *
|
from utils.firegexapi import FiregexAPI
|
||||||
from utils.tcpserver import TcpServer
|
from utils.tcpserver import TcpServer
|
||||||
from utils.udpserver import UdpServer
|
from utils.udpserver import UdpServer
|
||||||
import argparse, secrets, base64,time
|
import argparse
|
||||||
|
import secrets
|
||||||
|
import time
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
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("--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()
|
args = parser.parse_args()
|
||||||
sep()
|
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)
|
puts(f"{args.address}", color=colors.yellow)
|
||||||
|
|
||||||
firegex = FiregexAPI(args.address)
|
firegex = FiregexAPI(args.address)
|
||||||
|
|
||||||
#Login
|
#Login
|
||||||
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
|
if (firegex.login(args.password)):
|
||||||
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
puts("Sucessfully logged in ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Unknown response or wrong passowrd ✗", color=colors.red)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
#Create server
|
#Create server
|
||||||
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port+1,ipv6=args.ipv6,proxy_port=args.port)
|
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):
|
def exit_test(code):
|
||||||
if service_id:
|
if service_id:
|
||||||
server.stop()
|
server.stop()
|
||||||
if(firegex.ph_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
|
if(firegex.ph_delete_service(service_id)):
|
||||||
else: puts(f"Test Failed: Coulnd't delete serivce ✗", color=colors.red); exit_test(1)
|
puts("Sucessfully deleted service ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Coulnd't delete serivce ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
exit(code)
|
exit(code)
|
||||||
|
|
||||||
#Create and start serivce
|
#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")
|
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)
|
if service_id:
|
||||||
else: puts(f"Test Failed: Failed to create service ✗", color=colors.red); exit(1)
|
puts(f"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)
|
if(firegex.ph_start_service(service_id)):
|
||||||
else: puts(f"Test Failed: Failed to start service ✗", color=colors.red); exit_test(1)
|
puts("Sucessfully started service ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Failed to start service ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
|
||||||
server.start()
|
server.start()
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
@@ -48,33 +62,49 @@ time.sleep(0.5)
|
|||||||
#Check if it started
|
#Check if it started
|
||||||
def checkData(should_work):
|
def checkData(should_work):
|
||||||
res = None
|
res = None
|
||||||
try: res = server.sendCheckData(secrets.token_bytes(432))
|
try:
|
||||||
except (ConnectionRefusedError, TimeoutError): res = None
|
res = server.sendCheckData(secrets.token_bytes(432))
|
||||||
|
except (ConnectionRefusedError, TimeoutError):
|
||||||
|
res = None
|
||||||
if res:
|
if res:
|
||||||
if should_work: puts(f"Successfully received data ✔", color=colors.green)
|
if should_work:
|
||||||
else: puts("Test Failed: Connection wasn't blocked ✗", color=colors.red); exit_test(1)
|
puts("Successfully received data ✔", color=colors.green)
|
||||||
else:
|
else:
|
||||||
if should_work: puts(f"Test Failed: Data wans't received ✗", color=colors.red); exit_test(1)
|
puts("Test Failed: Connection wasn't blocked ✗", color=colors.red)
|
||||||
else: puts(f"Successfully blocked connection ✔", color=colors.green)
|
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)
|
checkData(True)
|
||||||
|
|
||||||
#Pause the proxy
|
#Pause the proxy
|
||||||
if(firegex.ph_stop_service(service_id)): puts(f"Sucessfully paused service with id {service_id} ✔", color=colors.green)
|
if(firegex.ph_stop_service(service_id)):
|
||||||
else: puts(f"Test Failed: Coulnd't pause the service ✗", color=colors.red); exit_test(1)
|
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)
|
checkData(False)
|
||||||
|
|
||||||
#Start firewall
|
#Start firewall
|
||||||
if(firegex.ph_start_service(service_id)): puts(f"Sucessfully started service with id {service_id} ✔", color=colors.green)
|
if(firegex.ph_start_service(service_id)):
|
||||||
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
|
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)
|
checkData(True)
|
||||||
|
|
||||||
#Change port
|
#Change port
|
||||||
if(firegex.ph_change_destination(service_id, "::1" if args.ipv6 else "127.0.0.1", args.port+2)):
|
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)
|
puts("Sucessfully changed port ✔", color=colors.green)
|
||||||
else: puts(f"Test Failed: Coulnd't change destination ✗", color=colors.red); exit_test(1)
|
else:
|
||||||
|
puts("Test Failed: Coulnd't change destination ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
|
||||||
checkData(False)
|
checkData(False)
|
||||||
|
|
||||||
@@ -86,14 +116,17 @@ time.sleep(0.5)
|
|||||||
checkData(True)
|
checkData(True)
|
||||||
|
|
||||||
#Rename service
|
#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)
|
if(firegex.ph_rename_service(service_id,f"{args.service_name}2")):
|
||||||
else: puts(f"Test Failed: Coulnd't rename service ✗", color=colors.red); exit_test(1)
|
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
|
#Check if service was renamed correctly
|
||||||
for services in firegex.ph_get_services():
|
for services in firegex.ph_get_services():
|
||||||
if services["name"] == f"{args.service_name}2":
|
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)
|
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)
|
exit_test(1)
|
||||||
|
|||||||
249
tests/px_test.py
249
tests/px_test.py
@@ -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)
|
|
||||||
@@ -18,8 +18,6 @@ echo "Running Netfilter Regex UDP ipv4"
|
|||||||
python3 nf_test.py -p $PASSWORD -m udp || ERROR=1
|
python3 nf_test.py -p $PASSWORD -m udp || ERROR=1
|
||||||
echo "Running Netfilter Regex UDP ipv6"
|
echo "Running Netfilter Regex UDP ipv6"
|
||||||
python3 nf_test.py -p $PASSWORD -m udp -6 || ERROR=1
|
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"
|
echo "Running Port Hijack TCP ipv4"
|
||||||
python3 ph_test.py -p $PASSWORD -m tcp || ERROR=1
|
python3 ph_test.py -p $PASSWORD -m tcp || ERROR=1
|
||||||
echo "Running Port Hijack TCP ipv6"
|
echo "Running Port Hijack TCP ipv6"
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class FiregexAPI:
|
|||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def reset(self, delete: bool):
|
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
|
#Netfilter regex
|
||||||
def nf_get_stats(self):
|
def nf_get_stats(self):
|
||||||
@@ -121,9 +121,9 @@ class FiregexAPI:
|
|||||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/disable")
|
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/disable")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_add_regex(self, service_id: str, regex: str, mode: str, active: bool, is_blacklist: bool, is_case_sensitive: bool):
|
def nf_add_regex(self, service_id: str, regex: str, mode: str, active: bool, is_case_sensitive: bool):
|
||||||
req = self.s.post(f"{self.address}api/nfregex/regexes/add",
|
req = self.s.post(f"{self.address}api/nfregex/regexes/add",
|
||||||
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_blacklist": is_blacklist, "is_case_sensitive": is_case_sensitive})
|
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_case_sensitive": is_case_sensitive})
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_add_service(self, name: str, port: int, proto: str, ip_int: str):
|
def nf_add_service(self, name: str, port: int, proto: str, ip_int: str):
|
||||||
@@ -131,84 +131,6 @@ class FiregexAPI:
|
|||||||
json={"name":name,"port":port, "proto": proto, "ip_int": ip_int})
|
json={"name":name,"port":port, "proto": proto, "ip_int": ip_int})
|
||||||
return req.json()["service_id"] if verify(req) else False
|
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
|
#PortHijack
|
||||||
def ph_get_services(self):
|
def ph_get_services(self):
|
||||||
req = self.s.get(f"{self.address}api/porthijack/services")
|
req = self.s.get(f"{self.address}api/porthijack/services")
|
||||||
|
|||||||
Reference in New Issue
Block a user