Moduled Firegex, Merging pt1 (not finished and not working yet)

This commit is contained in:
DomySh
2022-07-21 20:25:39 +02:00
parent 3143f6e474
commit 63ba0e94e7
42 changed files with 2832 additions and 577 deletions

View File

@@ -2,7 +2,7 @@
FROM python:alpine
RUN apk update
RUN apk add g++ git pcre2-dev libnetfilter_queue-dev libpcap-dev libcrypto1.1 libnfnetlink-dev libmnl-dev make cmake nftables
RUN apk add g++ git pcre2-dev libnetfilter_queue-dev libpcap-dev libcrypto1.1 libnfnetlink-dev libmnl-dev make cmake nftables boost-dev
WORKDIR /tmp/
RUN git clone --single-branch --branch release https://github.com/jpcre2/jpcre2
@@ -18,7 +18,9 @@ WORKDIR /execute
COPY ./backend/nfqueue /execute/nfqueue
ARG GCC_PARAMS
RUN g++ nfqueue/nfqueue.cpp -o modules/cppqueue -std=c++20 -O3 -march=native -lnetfilter_queue -pthread -lpcre2-8 -ltins -lmnl -lnfnetlink
RUN g++ -O3 -march=native $GCC_PARAMS -o modules/proxy nfqueue/proxy.cpp -pthread -lboost_system -lboost_thread -lpcre2-8
ADD ./backend/requirements.txt /execute/requirements.txt
RUN pip3 install --no-cache-dir -r /execute/requirements.txt

View File

@@ -6,8 +6,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import jwt
from passlib.context import CryptContext
from fastapi_socketio import SocketManager
from modules import SQLite
from modules.firegex import FiregexTables
from utils.sqlite import SQLite
from utils import API_VERSION, FIREGEX_PORT, JWT_ALGORITHM, get_interfaces, refresh_frontend, DEBUG
from utils.loader import frontend_deploy, load_routers
from utils.models import ChangePasswordModel, IpInterface, PasswordChangeForm, PasswordForm, ResetRequest, StatusModel, StatusMessageModel

View File

@@ -1,2 +0,0 @@
from .firewall import FirewallManager
from .sqlite import SQLite

View File

@@ -1,276 +0,0 @@
from typing import Dict, List, Set
from utils import ip_parse, ip_family, run_func
from modules.sqlite import Service
import re, os, asyncio
import traceback, nftables
from modules.sqlite import Regex
QUEUE_BASE_NUM = 1000
class FiregexFilter():
def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None):
self.nftables = nftables.Nftables()
self.id = int(id) if id else None
self.queue = queue
self.target = target
self.proto = proto
self.port = int(port)
self.ip_int = str(ip_int)
def __eq__(self, o: object) -> bool:
if isinstance(o, FiregexFilter):
return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
return False
class FiregexTables:
def __init__(self):
self.table_name = "firegex"
self.nft = nftables.Nftables()
def raw_cmd(self, *cmds):
return self.nft.json_cmd({"nftables": list(cmds)})
def cmd(self, *cmds):
code, out, err = self.raw_cmd(*cmds)
if code == 0: return out
else: raise Exception(err)
def init(self):
self.reset()
code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}})
if code == 0:
self.cmd(
{"create":{"chain":{
"family":"inet",
"table":self.table_name,
"name":"input",
"type":"filter",
"hook":"prerouting",
"prio":-150,
"policy":"accept"
}}},
{"create":{"chain":{
"family":"inet",
"table":self.table_name,
"name":"output",
"type":"filter",
"hook":"postrouting",
"prio":-150,
"policy":"accept"
}}}
)
def reset(self):
self.raw_cmd(
{"flush":{"table":{"name":"firegex","family":"inet"}}},
{"delete":{"table":{"name":"firegex","family":"inet"}}},
)
def list(self):
return self.cmd({"list": {"ruleset": None}})["nftables"]
def add_output(self, queue_range, proto, port, ip_int):
init, end = queue_range
if init > end: init, end = end, init
ip_int = ip_parse(ip_int)
ip_addr = str(ip_int).split("/")[0]
ip_addr_cidr = int(str(ip_int).split("/")[1])
self.cmd({ "insert":{ "rule": {
"family": "inet",
"table": self.table_name,
"chain": "output",
"expr": [
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'saddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, #ip_int
{'match': {'left': {'meta': {'key': 'l4proto'}}, 'op': '==', 'right': str(proto)}},
{'match': {"left": { "payload": {"protocol": str(proto), "field": "sport"}}, "op": "==", "right": int(port)}},
{"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}}
]
}}})
def add_input(self, queue_range, proto = None, port = None, ip_int = None):
init, end = queue_range
if init > end: init, end = end, init
ip_int = ip_parse(ip_int)
ip_addr = str(ip_int).split("/")[0]
ip_addr_cidr = int(str(ip_int).split("/")[1])
self.cmd({"insert":{"rule":{
"family": "inet",
"table": self.table_name,
"chain": "input",
"expr": [
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'daddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, #ip_int
{'match': {"left": { "payload": {"protocol": str(proto), "field": "dport"}}, "op": "==", "right": int(port)}},
{"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}}
]
}}})
def get(self) -> List[FiregexFilter]:
res = []
for filter in [ele["rule"] for ele in self.list() if "rule" in ele and ele["rule"]["table"] == self.table_name]:
queue_str = str(filter["expr"][2]["queue"]["num"]).split("-")
queue = None
if len(queue_str) == 1: queue = int(queue_str[0]), int(queue_str[0])
else: queue = int(queue_str[0]), int(queue_str[1])
ip_int = None
if isinstance(filter["expr"][0]["match"]["right"],str):
ip_int = str(ip_parse(filter["expr"][0]["match"]["right"]))
else:
ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}'
res.append(FiregexFilter(
target=filter["chain"],
id=int(filter["handle"]),
queue=queue,
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
port=filter["expr"][1]["match"]["right"],
ip_int=ip_int
))
return res
async def add(self, filter:FiregexFilter):
if filter in self.get(): return None
return await FiregexInterceptor.start( filter=filter, n_queues=int(os.getenv("N_THREADS_NFQUEUE","1")))
def delete_by_srv(self, srv:Service):
for filter in self.get():
if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int):
self.cmd({"delete":{"rule": {"handle": filter.id, "table": self.table_name, "chain": filter.target, "family": "inet"}}})
class RegexFilter:
def __init__(
self, regex,
is_case_sensitive=True,
is_blacklist=True,
input_mode=False,
output_mode=False,
blocked_packets=0,
id=None,
update_func = None
):
self.regex = regex
self.is_case_sensitive = is_case_sensitive
self.is_blacklist = is_blacklist
if input_mode == output_mode: input_mode = output_mode = True # (False, False) == (True, True)
self.input_mode = input_mode
self.output_mode = output_mode
self.blocked = blocked_packets
self.id = id
self.update_func = update_func
self.compiled_regex = self.compile()
@classmethod
def from_regex(cls, regex:Regex, update_func = None):
return cls(
id=regex.id, regex=regex.regex, is_case_sensitive=regex.is_case_sensitive,
is_blacklist=regex.is_blacklist, blocked_packets=regex.blocked_packets,
input_mode = regex.mode in ["C","B"], output_mode=regex.mode in ["S","B"],
update_func = update_func
)
def compile(self):
if isinstance(self.regex, str): self.regex = self.regex.encode()
if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether")
re.compile(self.regex) # raise re.error if it's invalid!
case_sensitive = "1" if self.is_case_sensitive else "0"
if self.input_mode:
yield case_sensitive + "C" + self.regex.hex() if self.is_blacklist else case_sensitive + "c"+ self.regex.hex()
if self.output_mode:
yield case_sensitive + "S" + self.regex.hex() if self.is_blacklist else case_sensitive + "s"+ self.regex.hex()
async def update(self):
if self.update_func:
await run_func(self.update_func, self)
class FiregexInterceptor:
def __init__(self):
self.filter:FiregexFilter
self.filter_map_lock:asyncio.Lock
self.filter_map: Dict[str, RegexFilter]
self.regex_filters: Set[RegexFilter]
self.update_config_lock:asyncio.Lock
self.process:asyncio.subprocess.Process
self.n_queues:int
self.update_task: asyncio.Task
@classmethod
async def start(cls, filter: FiregexFilter, n_queues:int = 1):
self = cls()
self.filter = filter
self.n_queues = n_queues
self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock()
input_range, output_range = await self._start_binary()
self.update_task = asyncio.create_task(self.update_blocked())
FiregexTables().add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
FiregexTables().add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
return self
async def _start_binary(self):
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"./cppqueue")
self.process = await asyncio.create_subprocess_exec(
proxy_binary_path, str(self.n_queues),
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
)
line_fut = self.process.stdout.readuntil()
try:
line_fut = await asyncio.wait_for(line_fut, timeout=3)
except asyncio.TimeoutError:
self.process.kill()
raise Exception("Invalid binary output")
line = line_fut.decode()
if line.startswith("QUEUES "):
params = line.split()
return (int(params[2]), int(params[3])), (int(params[5]), int(params[6]))
else:
self.process.kill()
raise Exception("Invalid binary output")
async def update_blocked(self):
try:
while True:
line = (await self.process.stdout.readuntil()).decode()
if line.startswith("BLOCKED"):
regex_id = line.split()[1]
async with self.filter_map_lock:
if regex_id in self.filter_map:
self.filter_map[regex_id].blocked+=1
await self.filter_map[regex_id].update()
except asyncio.CancelledError: pass
except asyncio.IncompleteReadError: pass
except Exception:
traceback.print_exc()
async def stop(self):
self.update_task.cancel()
if self.process and self.process.returncode is None:
self.process.kill()
async def _update_config(self, filters_codes):
async with self.update_config_lock:
self.process.stdin.write((" ".join(filters_codes)+"\n").encode())
await self.process.stdin.drain()
async def reload(self, filters:List[RegexFilter]):
async with self.filter_map_lock:
self.filter_map = self.compile_filters(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 compile_filters(self, filters:List[RegexFilter]):
res = {}
for filter_obj in filters:
try:
raw_filters = filter_obj.compile()
for filter in raw_filters:
res[filter] = filter_obj
except Exception: pass
return res

View File

View File

@@ -0,0 +1,151 @@
from typing import Dict, List, Set
from utils.firegextables import FiregexFilter, FiregexTables
from utils import ip_parse, ip_family, run_func
from modules.nfregex.models import Service, Regex
import re, os, asyncio
import traceback
QUEUE_BASE_NUM = 1000
class RegexFilter:
def __init__(
self, regex,
is_case_sensitive=True,
is_blacklist=True,
input_mode=False,
output_mode=False,
blocked_packets=0,
id=None,
update_func = None
):
self.regex = regex
self.is_case_sensitive = is_case_sensitive
self.is_blacklist = is_blacklist
if input_mode == output_mode: input_mode = output_mode = True # (False, False) == (True, True)
self.input_mode = input_mode
self.output_mode = output_mode
self.blocked = blocked_packets
self.id = id
self.update_func = update_func
self.compiled_regex = self.compile()
@classmethod
def from_regex(cls, regex:Regex, update_func = None):
return cls(
id=regex.id, regex=regex.regex, is_case_sensitive=regex.is_case_sensitive,
is_blacklist=regex.is_blacklist, blocked_packets=regex.blocked_packets,
input_mode = regex.mode in ["C","B"], output_mode=regex.mode in ["S","B"],
update_func = update_func
)
def compile(self):
if isinstance(self.regex, str): self.regex = self.regex.encode()
if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether")
re.compile(self.regex) # raise re.error if it's invalid!
case_sensitive = "1" if self.is_case_sensitive else "0"
if self.input_mode:
yield case_sensitive + "C" + self.regex.hex() if self.is_blacklist else case_sensitive + "c"+ self.regex.hex()
if self.output_mode:
yield case_sensitive + "S" + self.regex.hex() if self.is_blacklist else case_sensitive + "s"+ self.regex.hex()
async def update(self):
if self.update_func:
await run_func(self.update_func, self)
class FiregexInterceptor:
def __init__(self):
self.filter:FiregexFilter
self.filter_map_lock:asyncio.Lock
self.filter_map: Dict[str, RegexFilter]
self.regex_filters: Set[RegexFilter]
self.update_config_lock:asyncio.Lock
self.process:asyncio.subprocess.Process
self.n_queues:int
self.update_task: asyncio.Task
@classmethod
async def start(cls, filter: FiregexFilter, n_queues:int = int(os.getenv("NTHREADS","1"))):
self = cls()
self.filter = filter
self.n_queues = n_queues
self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock()
input_range, output_range = await self._start_binary()
self.update_task = asyncio.create_task(self.update_blocked())
if not filter in FiregexTables().get():
FiregexTables().add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
FiregexTables().add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
return self
async def _start_binary(self):
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cppqueue")
self.process = await asyncio.create_subprocess_exec(
proxy_binary_path, str(self.n_queues),
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
)
line_fut = self.process.stdout.readuntil()
try:
line_fut = await asyncio.wait_for(line_fut, timeout=3)
except asyncio.TimeoutError:
self.process.kill()
raise Exception("Invalid binary output")
line = line_fut.decode()
if line.startswith("QUEUES "):
params = line.split()
return (int(params[2]), int(params[3])), (int(params[5]), int(params[6]))
else:
self.process.kill()
raise Exception("Invalid binary output")
async def update_blocked(self):
try:
while True:
line = (await self.process.stdout.readuntil()).decode()
if line.startswith("BLOCKED"):
regex_id = line.split()[1]
async with self.filter_map_lock:
if regex_id in self.filter_map:
self.filter_map[regex_id].blocked+=1
await self.filter_map[regex_id].update()
except asyncio.CancelledError: pass
except asyncio.IncompleteReadError: pass
except Exception:
traceback.print_exc()
async def stop(self):
self.update_task.cancel()
if self.process and self.process.returncode is None:
self.process.kill()
async def _update_config(self, filters_codes):
async with self.update_config_lock:
self.process.stdin.write((" ".join(filters_codes)+"\n").encode())
await self.process.stdin.drain()
async def reload(self, filters:List[RegexFilter]):
async with self.filter_map_lock:
self.filter_map = self.compile_filters(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 compile_filters(self, filters:List[RegexFilter]):
res = {}
for filter_obj in filters:
try:
raw_filters = filter_obj.compile()
for filter in raw_filters:
res[filter] = filter_obj
except Exception: pass
return res
def delete_by_srv(srv:Service):
nft = FiregexTables()
for filter in nft.get():
if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int):
nft.cmd({"delete":{"rule": {"handle": filter.id, "table": nft.table_name, "chain": filter.target, "family": "inet"}}})

View File

@@ -1,7 +1,8 @@
import asyncio
from typing import Dict
from modules.firegex import FiregexFilter, FiregexTables, RegexFilter
from modules.sqlite import Regex, SQLite, Service
from modules.nfregex.firegex import FiregexFilter, FiregexInterceptor, FiregexTables, RegexFilter, delete_by_srv
from modules.nfregex.models import Regex, Service
from utils.sqlite import SQLite
class STATUS:
STOP = "stop"
@@ -93,13 +94,13 @@ class ServiceManager:
async def start(self):
if not self.interceptor:
FiregexTables().delete_by_srv(self.srv)
self.interceptor = await FiregexTables().add(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int))
delete_by_srv(self.srv)
self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int))
await self._update_filters_from_db()
self._set_status(STATUS.ACTIVE)
async def stop(self):
FiregexTables().delete_by_srv(self.srv)
delete_by_srv(self.srv)
if self.interceptor:
await self.interceptor.stop()
self.interceptor = None

View File

@@ -0,0 +1,30 @@
import base64
class Service:
def __init__(self, id: str, status: str, port: int, name: str, proto: str, ip_int: str):
self.id = id
self.status = status
self.port = port
self.name = name
self.proto = proto
self.ip_int = ip_int
@classmethod
def from_dict(cls, var: dict):
return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"])
class Regex:
def __init__(self, id: int, regex: bytes, mode: str, service_id: str, is_blacklist: bool, blocked_packets: int, is_case_sensitive: bool, active: bool):
self.regex = regex
self.mode = mode
self.service_id = service_id
self.is_blacklist = is_blacklist
self.blocked_packets = blocked_packets
self.id = id
self.is_case_sensitive = is_case_sensitive
self.active = active
@classmethod
def from_dict(cls, var: dict):
return cls(id=var["regex_id"], regex=base64.b64decode(var["regex"]), mode=var["mode"], service_id=var["service_id"], is_blacklist=var["is_blacklist"], blocked_packets=var["blocked_packets"], is_case_sensitive=var["is_case_sensitive"], active=var["active"])

BIN
backend/modules/proxy Executable file

Binary file not shown.

View File

View File

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

View File

@@ -0,0 +1,196 @@
import secrets
from modules.regexproxy.proxy import Filter, Proxy
import random, socket, asyncio
from base64 import b64decode
from utils.sqlite import SQLite
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 self.proxy.start(in_pause=(to==STATUS.PAUSE))
self._set_status(STATUS.STOP)
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 = {}
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):
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

497
backend/nfqueue/proxy.cpp Normal file
View File

@@ -0,0 +1,497 @@
/*
Copyright (c) 2007 Arash Partow (http://www.partow.net)
URL: http://www.partow.net/programming/tcpproxy/index.html
Modified and adapted by Pwnzer0tt1
*/
#include <cstdlib>
#include <cstddef>
#include <iostream>
#include <string>
#include <mutex>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread/mutex.hpp>
#include <jpcre2.hpp>
typedef jpcre2::select<char> jp;
using namespace std;
bool unhexlify(string const &hex, string &newString) {
try{
int len = hex.length();
for(int i=0; i< len; i+=2)
{
std::string byte = hex.substr(i,2);
char chr = (char) (int)strtol(byte.c_str(), NULL, 16);
newString.push_back(chr);
}
return true;
}
catch (...){
return false;
}
}
typedef pair<string,jp::Regex> regex_rule_pair;
typedef vector<regex_rule_pair> regex_rule_vector;
struct regex_rules{
regex_rule_vector regex_s_c_w, regex_c_s_w, regex_s_c_b, regex_c_s_b;
regex_rule_vector* getByCode(char code){
switch(code){
case 'C': // Client to server Blacklist
return &regex_c_s_b; break;
case 'c': // Client to server Whitelist
return &regex_c_s_w; break;
case 'S': // Server to client Blacklist
return &regex_s_c_b; break;
case 's': // Server to client Whitelist
return &regex_s_c_w; break;
}
throw invalid_argument( "Expected 'C' 'c' 'S' or 's'" );
}
void add(const char* arg){
//Integrity checks
size_t arg_len = strlen(arg);
if (arg_len < 2 || arg_len%2 != 0) return;
if (arg[0] != '0' && arg[0] != '1') return;
if (arg[1] != 'C' && arg[1] != 'c' && arg[1] != 'S' && arg[1] != 's') return;
string hex(arg+2), expr;
if (!unhexlify(hex, expr)) return;
//Push regex
jp::Regex regex(expr,arg[0] == '1'?"gS":"giS");
if (regex){
#ifdef DEBUG
cerr << "Added regex " << expr << " " << arg << endl;
#endif
getByCode(arg[1])->push_back(make_pair(string(arg), regex));
} else {
cerr << "Regex " << arg << " was not compiled successfully" << endl;
}
}
};
shared_ptr<regex_rules> regex_config;
mutex update_mutex;
#ifdef MULTI_THREAD
mutex stdout_mutex;
#endif
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)){
#ifdef MULTI_THREAD
std::unique_lock<std::mutex> lck(stdout_mutex);
#endif
cout << "BLOCKED " << ele.first << endl;
return false;
}
} catch(...){
cerr << "Error while matching regex: " << ele.first << endl;
}
}
for (regex_rule_pair ele:whitelist){
try{
if(!ele.second.match(str_data)){
#ifdef MULTI_THREAD
std::unique_lock<std::mutex> lck(stdout_mutex);
#endif
cout << "BLOCKED " << ele.first << endl;
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];
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();
#ifdef MULTI_THREAD
boost::thread_group tg;
#ifdef THREAD_NUM
for (unsigned i = 0; i < THREAD_NUM; ++i)
#else
for (unsigned i = 0; i < thread::hardware_concurrency(); ++i)
#endif
tg.create_thread(boost::bind(&boost::asio::io_context::run, &ios));
tg.join_all();
#else
ios.run();
#endif
}
catch(exception& e)
{
cerr << "Error: " << e.what() << endl;
return 1;
}
#ifdef DEBUG
cerr << "Proxy stopped!" << endl;
#endif
return 0;
}

BIN
backend/regextcpproxy.db Normal file

Binary file not shown.

View File

@@ -1,13 +1,14 @@
from base64 import b64decode
import re
import secrets
import sqlite3
from typing import List, Union
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from modules.firegex import FiregexTables
from modules.firewall import STATUS, FirewallManager
from modules.sqlite import SQLite
from utils import gen_service_id, ip_parse, refactor_name, refresh_frontend
from modules.nfregex.firegex import FiregexTables
from modules.nfregex.firewall import STATUS, FirewallManager
from utils.sqlite import SQLite
from utils import ip_parse, refactor_name, refresh_frontend
from utils.models import ResetRequest, StatusMessageModel
class GeneralStatModel(BaseModel):
@@ -58,29 +59,6 @@ class ServiceAddResponse(BaseModel):
app = APIRouter()
async def reset(params: ResetRequest):
if not params.delete:
db.backup()
await firewall.close()
FiregexTables().reset()
if params.delete:
db.delete()
db.init()
else:
db.restore()
await firewall.init()
async def startup():
db.init()
await firewall.init()
async def shutdown():
db.backup()
await firewall.close()
db.disconnect()
db.restore()
db = SQLite('db/nft-regex.db', {
'services': {
'service_id': 'VARCHAR(100) PRIMARY KEY',
@@ -107,6 +85,36 @@ db = SQLite('db/nft-regex.db', {
]
})
async def reset(params: ResetRequest):
if not params.delete:
db.backup()
await firewall.close()
FiregexTables().reset()
if params.delete:
db.delete()
db.init()
else:
db.restore()
await firewall.init()
async def startup():
db.init()
await firewall.init()
async def shutdown():
db.backup()
await firewall.close()
db.disconnect()
db.restore()
def gen_service_id():
while True:
res = secrets.token_hex(8)
if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0:
break
return res
firewall = FirewallManager(db)
@app.get('/stats', response_model=GeneralStatModel)
@@ -271,7 +279,7 @@ async def add_new_service(form: ServiceAddForm, ):
return {"status":"Invalid protocol"}
srv_id = None
try:
srv_id = gen_service_id(db)
srv_id = gen_service_id()
db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?)",
srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int)
except sqlite3.IntegrityError:

View File

@@ -0,0 +1,297 @@
from base64 import b64decode
import sqlite3, re
from typing import List, Union
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.firegextables import FiregexTables
app = APIRouter()
db = SQLite("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'
},
'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()
FiregexTables().reset()
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()
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: int
internal_port: int
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)
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)
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)
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)
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()
return {'status': 'ok'}
class ChangePortForm(BaseModel):
port: Union[int, None]
internalPort: Union[int, 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:
return {'status': 'Invalid Request!'}
try:
sql_inj = ""
query:List[Union[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:
return {'status': 'Name or/and port of the service has been already assigned to another service'}
await firewall.get(service_id).update_port()
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"""
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()
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()
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()
return {'status': 'ok'}
class RegexAddForm(BaseModel):
service_id: str
regex: str
mode: str
active: Union[bool,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:
return {"status":"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:
return {'status': 'An identical regex already exists'}
await firewall.get(form.service_id).update_filters()
return {'status': 'ok'}
class ServiceAddForm(BaseModel):
name: str
port: int
internalPort: Union[int, None]
class ServiceAddStatus(BaseModel):
status:str
id: Union[str,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"""
if not form.name: return {'status': 'The name cannot be empty!'}
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
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)
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:
return {'status': 'Name or/and ports of the service has been already assigned to another service'}
await firewall.reload()
return {'status': 'ok', "id": serv_id }

View File

@@ -1,6 +1,6 @@
import asyncio
from ipaddress import ip_interface
import os, socket, secrets, psutil
import os, socket, psutil
import sys
from fastapi_socketio import SocketManager
@@ -30,13 +30,6 @@ def refactor_name(name:str):
while " " in name: name = name.replace(" "," ")
return name
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 list_files(mypath):
from os import listdir
from os.path import isfile, join

View File

@@ -0,0 +1,125 @@
from typing import List
import nftables
from utils import ip_parse, ip_family
class FiregexFilter():
def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None):
self.nftables = nftables.Nftables()
self.id = int(id) if id else None
self.queue = queue
self.target = target
self.proto = proto
self.port = int(port)
self.ip_int = str(ip_int)
def __eq__(self, o: object) -> bool:
if isinstance(o, FiregexFilter):
return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
return False
class FiregexTables:
def __init__(self):
self.table_name = "firegex"
self.nft = nftables.Nftables()
def raw_cmd(self, *cmds):
return self.nft.json_cmd({"nftables": list(cmds)})
def cmd(self, *cmds):
code, out, err = self.raw_cmd(*cmds)
if code == 0: return out
else: raise Exception(err)
def init(self):
self.reset()
code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}})
if code == 0:
self.cmd(
{"create":{"chain":{
"family":"inet",
"table":self.table_name,
"name":"input",
"type":"filter",
"hook":"prerouting",
"prio":-150,
"policy":"accept"
}}},
{"create":{"chain":{
"family":"inet",
"table":self.table_name,
"name":"output",
"type":"filter",
"hook":"postrouting",
"prio":-150,
"policy":"accept"
}}}
)
def reset(self):
self.raw_cmd(
{"flush":{"table":{"name":"firegex","family":"inet"}}},
{"delete":{"table":{"name":"firegex","family":"inet"}}},
)
def list(self):
return self.cmd({"list": {"ruleset": None}})["nftables"]
def add_output(self, queue_range, proto, port, ip_int):
init, end = queue_range
if init > end: init, end = end, init
ip_int = ip_parse(ip_int)
ip_addr = str(ip_int).split("/")[0]
ip_addr_cidr = int(str(ip_int).split("/")[1])
self.cmd({ "insert":{ "rule": {
"family": "inet",
"table": self.table_name,
"chain": "output",
"expr": [
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'saddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}},
{'match': {"left": { "payload": {"protocol": str(proto), "field": "sport"}}, "op": "==", "right": int(port)}},
{"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}}
]
}}})
def add_input(self, queue_range, proto = None, port = None, ip_int = None):
init, end = queue_range
if init > end: init, end = end, init
ip_int = ip_parse(ip_int)
ip_addr = str(ip_int).split("/")[0]
ip_addr_cidr = int(str(ip_int).split("/")[1])
self.cmd({"insert":{"rule":{
"family": "inet",
"table": self.table_name,
"chain": "input",
"expr": [
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'daddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}},
{'match': {"left": { "payload": {"protocol": str(proto), "field": "dport"}}, "op": "==", "right": int(port)}},
{"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}}
]
}}})
def get(self) -> List[FiregexFilter]:
res = []
for filter in [ele["rule"] for ele in self.list() if "rule" in ele and ele["rule"]["table"] == self.table_name]:
queue_str = str(filter["expr"][2]["queue"]["num"]).split("-")
queue = None
if len(queue_str) == 1: queue = int(queue_str[0]), int(queue_str[0])
else: queue = int(queue_str[0]), int(queue_str[1])
ip_int = None
if isinstance(filter["expr"][0]["match"]["right"],str):
ip_int = str(ip_parse(filter["expr"][0]["match"]["right"]))
else:
ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}'
res.append(FiregexFilter(
target=filter["chain"],
id=int(filter["handle"]),
queue=queue,
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
port=filter["expr"][1]["match"]["right"],
ip_int=ip_int
))
return res

View File

@@ -93,33 +93,3 @@ class SQLite():
self.query('INSERT INTO keys_values (key, value) VALUES (?, ?);', key, str(value))
else:
self.query('UPDATE keys_values SET value=? WHERE key = ?;', str(value), key)
class Service:
def __init__(self, id: str, status: str, port: int, name: str, proto: str, ip_int: str):
self.id = id
self.status = status
self.port = port
self.name = name
self.proto = proto
self.ip_int = ip_int
@classmethod
def from_dict(cls, var: dict):
return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"])
class Regex:
def __init__(self, id: int, regex: bytes, mode: str, service_id: str, is_blacklist: bool, blocked_packets: int, is_case_sensitive: bool, active: bool):
self.regex = regex
self.mode = mode
self.service_id = service_id
self.is_blacklist = is_blacklist
self.blocked_packets = blocked_packets
self.id = id
self.is_case_sensitive = is_case_sensitive
self.active = active
@classmethod
def from_dict(cls, var: dict):
return cls(id=var["regex_id"], regex=base64.b64decode(var["regex"]), mode=var["mode"], service_id=var["service_id"], is_blacklist=var["is_blacklist"], blocked_packets=var["blocked_packets"], is_case_sensitive=var["is_case_sensitive"], active=var["active"])

View File

@@ -2,7 +2,6 @@
@use "../../index.scss" as *;
.footer{
height: 150px;
margin-top: 50px;
background-color: $primary_color;
@extend .center-flex;

View File

@@ -1,12 +1,13 @@
import { Footer } from '@mantine/core';
import React from 'react';
import style from "./index.module.scss";
function Footer() {
return <div id="footer" className={style.footer}>
function FooterPage() {
return <Footer id="footer" height={70} className={style.footer}>
Made by Pwnzer0tt1
</div>
</Footer>
}
export default Footer;
export default FooterPage;

View File

@@ -1,17 +1,24 @@
@use "../../vars" as *;
@use "../../index.scss" as *;
.header{
width: 100%;
height: 140px;
background-color: $primary_color;
display: flex;
align-items: center;
justify-content: center;
}
.logo{
width: 200px;
margin-left: 40px;
height: 70%;
.divlogo{
width: 110px;
height: 100%;
cursor: pointer;
@extend .center-flex;
}
.navbtn{
@extend .center-flex;
width: 30px;
margin:0;
}

View File

@@ -1,12 +1,9 @@
import React, { useState } from 'react';
import { ActionIcon, Divider, Image, Menu, Tooltip, FloatingTooltip } from '@mantine/core';
import { ActionIcon, Divider, Image, Menu, Tooltip, FloatingTooltip, MediaQuery, Burger, Space, Header } from '@mantine/core';
import style from "./index.module.scss";
import { errorNotify, logout } from '../../js/utils';
import { BsPlusLg } from "react-icons/bs"
import { errorNotify, gatmainpath, logout } from '../../js/utils';
import { AiFillHome } from "react-icons/ai"
import { useNavigate, useParams } from 'react-router-dom';
import AddNewRegex from '../NFRegex/AddNewRegex';
import AddNewService from '../NFRegex/AddNewService';
import { useNavigate } from 'react-router-dom';
import { FaLock } from 'react-icons/fa';
import { MdOutlineSettingsBackupRestore } from 'react-icons/md';
import { ImExit } from 'react-icons/im';
@@ -14,7 +11,7 @@ import ResetPasswordModal from './ResetPasswordModal';
import ResetModal from './ResetModal';
function Header() {
function HeaderPage({navOpen, setNav, ...other}: { navOpen: boolean, setNav:React.Dispatch<React.SetStateAction<boolean>>}) {
const navigator = useNavigate()
@@ -26,19 +23,27 @@ function Header() {
})
}
const go_to_home = () => {
navigator(`/${gatmainpath()}`)
}
const [changePasswordModal, setChangePasswordModal] = useState(false);
const [resetFiregexModal, setResetFiregexModal] = useState(false);
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
const [tooltipHomeOpened, setTooltipHomeOpened] = useState(false);
const [tooltipLogoutOpened,setTooltipLogoutOpened] = useState(false);
const {srv} = useParams()
const [open, setOpen] = useState(false);
const closeModal = () => {setOpen(false);}
return <div id="header-page" className={style.header}>
<div style={{ width: 240, marginLeft: 'auto', marginRight: 'auto', padding:"40px", cursor: 'pointer' }}>
return <Header height={100} className={style.header} {...other}>
<Space w="lg" />
<MediaQuery largerThan="md" styles={{ display: 'none' }}><div>
<Burger
opened={navOpen}
className={style.navbtn}
onClick={() => setNav((o) => !o)}
size="sm"
mr="xl"
/>
</div></MediaQuery>
<div className={style.divlogo}>
<FloatingTooltip zIndex={0} label="Home" transition="pop" transitionDuration={200} openDelay={1000} transitionTimingFunction="ease" color="dark" position="right" >
<Image src="/header-logo.png" alt="Firegex logo" onClick={()=>navigator("/")}/>
</FloatingTooltip>
@@ -46,50 +51,34 @@ function Header() {
<div className="flex-spacer" />
<div style={{marginLeft:"20px"}}></div>
<Menu>
<Menu.Label>Firewall Access</Menu.Label>
<Menu.Item icon={<ImExit size={14} />} onClick={logout_action}>Logout</Menu.Item>
<Menu.Item color="red" icon={<FaLock size={14} />} onClick={() => setChangePasswordModal(true)}>Change Password</Menu.Item>
<Menu.Item icon={<FaLock size={14} />} onClick={() => setChangePasswordModal(true)}>Change Password</Menu.Item>
<Divider />
<Menu.Label>Actions</Menu.Label>
<Menu.Item color="red" icon={<MdOutlineSettingsBackupRestore size={18} />} onClick={() => setResetFiregexModal(true)}>Reset Firegex</Menu.Item>
</Menu>
<div style={{marginLeft:"20px"}}></div>
<Tooltip zIndex={0} label="Home" position='bottom' transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="teal" opened={tooltipHomeOpened} tooltipId="tooltip-home-id">
<Space w="md" />
<Tooltip label="Home" position='bottom' transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="teal" opened={tooltipHomeOpened} tooltipId="tooltip-home-id">
<ActionIcon color="teal" style={{marginRight:"10px"}}
size="xl" radius="md" variant="filled"
onClick={()=>navigator("/")}
aria-describedby="tooltip-home-id"
onClick={go_to_home}
onFocus={() => setTooltipHomeOpened(false)} onBlur={() => setTooltipHomeOpened(false)}
onMouseEnter={() => setTooltipHomeOpened(true)} onMouseLeave={() => setTooltipHomeOpened(false)}>
<AiFillHome size="25px" />
</ActionIcon>
</Tooltip>
{ srv?
<Tooltip label="Add a new regex" zIndex={0} position='bottom' transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="blue" opened={tooltipAddOpened} tooltipId="tooltip-add-id">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-add-id"
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip>
: <Tooltip label="Add a new service" zIndex={0} position='bottom' transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="blue" opened={tooltipAddOpened} tooltipId="tooltip-add-id">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-add-id"
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip>
}
{srv?
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
<AddNewService opened={open} onClose={closeModal} />
}
<Tooltip label="Logout" position='bottom' transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="blue" opened={tooltipLogoutOpened} tooltipId="tooltip-add-id">
<ActionIcon color="blue" onClick={logout_action} size="xl" radius="md" variant="filled"
onFocus={() => setTooltipLogoutOpened(false)} onBlur={() => setTooltipLogoutOpened(false)}
onMouseEnter={() => setTooltipLogoutOpened(true)} onMouseLeave={() => setTooltipLogoutOpened(false)}><ImExit size={23} style={{marginTop:"3px", marginLeft:"2px"}}/></ActionIcon>
</Tooltip>
<ResetPasswordModal opened={changePasswordModal} onClose={() => setChangePasswordModal(false)} />
<ResetModal opened={resetFiregexModal} onClose={() => setResetFiregexModal(false)} />
<div style={{marginLeft:"40px"}}></div>
</div>
<Space w="xl" />
</Header>
}
export default Header;
export default HeaderPage;

View File

@@ -1,23 +1,33 @@
import React from 'react';
import { Container, Space, Tabs } from '@mantine/core';
import Footer from './Footer';
import Header from './Header';
import React, { useState } from 'react';
import { Container, Space } from '@mantine/core';
import { AppShell } from '@mantine/core';
import NavBar from './NavBar';
import FooterPage from './Footer';
import HeaderPage from './Header';
function MainLayout({ children }:{ children:any }) {
const [opened, setOpened] = useState(false);
return <>
<Header/>
<Tabs grow variant="pills">
<Tabs.Tab label="Regex Prox"></Tabs.Tab>
<Tabs.Tab label="Port Hijacking"></Tabs.Tab>
<Tabs.Tab label="Netfilter regex"></Tabs.Tab>
</Tabs>
<Space h="xl" />
<Container size="lg" style={{minHeight:"57.5vh"}}>
{children}
</Container>
<Space h="xl" />
<Footer />
</>
<AppShell
padding="md"
fixed
navbar={<NavBar closeNav={()=>setOpened(false)} opened={opened} />}
header={<HeaderPage navOpen={opened} setNav={setOpened} />}
footer={<FooterPage />}
>
<Container size="lg">
{children}
</Container>
<Space h="lg" />
</AppShell>
</>
}
export default MainLayout;

View File

@@ -0,0 +1,101 @@
import { ServerResponse } from "../../js/models"
import { getapi, postapi } from "../../js/utils"
export type GeneralStats = {
services:number,
closed:number,
regexes:number
}
export type Service = {
name:string,
service_id:string,
status:string,
port:number,
proto: string,
ip_int: string,
n_packets:number,
n_regex:number,
}
export type ServiceAddForm = {
name:string,
port:number,
proto:string,
ip_int:string,
}
export type ServiceAddResponse = {
status: string,
service_id?: string,
}
export type RegexFilter = {
id:number,
service_id:string,
regex:string
is_blacklist:boolean,
is_case_sensitive:boolean,
mode:string //C S B => C->S S->C BOTH
n_packets:number,
active:boolean
}
export type RegexAddForm = {
service_id:string,
regex:string,
is_case_sensitive:boolean,
is_blacklist:boolean,
mode:string, // C->S S->C BOTH,
active: boolean
}
export const nfregex = {
stats: async () => {
return await getapi("nfregex/stats") as GeneralStats;
},
services: async () => {
return await getapi("nfregex/services") as Service[];
},
serviceinfo: async (service_id:string) => {
return await getapi(`nfregex/service/${service_id}`) as Service;
},
regexdelete: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/delete`) as ServerResponse;
return status === "ok"?undefined:status
},
regexenable: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/enable`) as ServerResponse;
return status === "ok"?undefined:status
},
regexdisable: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/disable`) as ServerResponse;
return status === "ok"?undefined:status
},
servicestart: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/start`) as ServerResponse;
return status === "ok"?undefined:status
},
servicerename: async (service_id:string, name: string) => {
const { status } = await postapi(`nfregex/service/${service_id}/rename`,{ name }) as ServerResponse;
return status === "ok"?undefined:status
},
servicestop: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/stop`) as ServerResponse;
return status === "ok"?undefined:status
},
servicesadd: async (data:ServiceAddForm) => {
return await postapi("nfregex/services/add",data) as ServiceAddResponse;
},
servicedelete: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/delete`) as ServerResponse;
return status === "ok"?undefined:status
},
regexesadd: async (data:RegexAddForm) => {
const { status } = await postapi("nfregex/regexes/add",data) as ServerResponse;
return status === "ok"?undefined:status
},
serviceregexes: async (service_id:string) => {
return await getapi(`nfregex/service/${service_id}/regexes`) as RegexFilter[];
}
}

View File

@@ -0,0 +1,49 @@
import { Group, MantineColor, Navbar, ScrollArea, Text, ThemeIcon, Title, UnstyledButton } from "@mantine/core";
import React from "react";
import { IoMdGitNetwork } from "react-icons/io";
import { MdTransform } from "react-icons/md";
import { useNavigate } from "react-router-dom";
import { gatmainpath } from "../../js/utils";
import { GrDirections } from "react-icons/gr";
function NavBarButton({ navigate, closeNav, name, icon, color, disabled }:
{ navigate: string, closeNav: () => void, name:string, icon:any, color:MantineColor, disabled?:boolean }) {
const navigator = useNavigate()
return <UnstyledButton sx={(theme) => ({
display: 'block',
width: '100%',
padding: theme.spacing.xs,
borderRadius: theme.radius.sm,
opacity: disabled ? 0.4 : 1,
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
backgroundColor:(navigate===gatmainpath()?(theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0]):"transparent"),
'&:hover': {
backgroundColor:
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
},
})} onClick={()=>{navigator(`/${navigate}`);closeNav()}} disabled={disabled}>
<Group>
<ThemeIcon color={color} variant="light">
{icon}
</ThemeIcon>
<Text size="sm">{name}</Text>
</Group>
</UnstyledButton>
}
export default function NavBar({ closeNav, opened }: {closeNav: () => void, opened: boolean}) {
return <Navbar p="md" hiddenBreakpoint="md" hidden={!opened} width={{ md: 300 }}>
<Navbar.Section px="xs" mt="xs">
<Title order={3}>[Fi]*regex 🔥</Title>
</Navbar.Section>
<hr style={{width:"100%"}}/>
<Navbar.Section grow component={ScrollArea} px="xs" mt="xs">
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="blue" icon={<IoMdGitNetwork />} />
<NavBarButton navigate="regexproxy" closeNav={closeNav} name="TCP Proxy Regex Filter" color="lime" icon={<MdTransform />} />
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="red" icon={<GrDirections />} disabled/>
</Navbar.Section>
</Navbar>
}

View File

@@ -0,0 +1,124 @@
import { Button, Group, Space, TextInput, Notification, Switch, NativeSelect, Modal } from '@mantine/core';
import { useForm } from '@mantine/hooks';
import React, { useState } from 'react';
import { RegexAddForm } from '../js/models';
import { addregex, b64decode, b64encode, fireUpdateRequest, okNotify } from '../js/utils';
import { ImCross } from "react-icons/im"
import FilterTypeSelector from './FilterTypeSelector';
type RegexAddInfo = {
regex:string,
type:string,
mode:string,
is_case_insensitive:boolean,
deactive:boolean
}
function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:string }) {
const form = useForm({
initialValues: {
regex:"",
type:"blacklist",
mode:"C -> S",
is_case_insensitive:false,
deactive:false
},
validationRules:{
regex: (value) => value !== "",
type: (value) => ["blacklist","whitelist"].includes(value),
mode: (value) => ['C -> S', 'S -> C', 'C <-> S'].includes(value)
}
})
const close = () =>{
onClose()
form.reset()
setError(null)
}
const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null)
const submitRequest = (values:RegexAddInfo) => {
setSubmitLoading(true)
const filter_mode = ({'C -> S':'C', 'S -> C':'S', 'C <-> S':'B'}[values.mode])
const request:RegexAddForm = {
is_blacklist:values.type !== "whitelist",
is_case_sensitive: !values.is_case_insensitive,
service_id: service,
mode: filter_mode?filter_mode:"B",
regex: b64encode(values.regex),
active: !values.deactive
}
setSubmitLoading(false)
addregex(request).then( res => {
if (!res){
setSubmitLoading(false)
close();
fireUpdateRequest();
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`)
}else if (res.toLowerCase() === "invalid regex"){
setSubmitLoading(false)
form.setFieldError("regex", "Invalid Regex")
}else{
setSubmitLoading(false)
setError("Error: [ "+res+" ]")
}
}).catch( err => {
setSubmitLoading(false)
setError("Request Failed! [ "+err+" ]")
})
}
return <Modal size="xl" title="Add a new regex filter" opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}>
<TextInput
label="Regex"
placeholder="[A-Z0-9]{31}="
{...form.getInputProps('regex')}
/>
<Space h="md" />
<Switch
label="Case insensitive"
{...form.getInputProps('is_case_insensitive', { type: 'checkbox' })}
/>
<Space h="md" />
<Switch
label="Deactivate"
{...form.getInputProps('deactive', { type: 'checkbox' })}
/>
<Space h="md" />
<NativeSelect
data={['C -> S', 'S -> C', 'C <-> S']}
label="Choose the source of the packets to filter"
variant="filled"
{...form.getInputProps('mode')}
/>
<Space h="md" />
<FilterTypeSelector
size="md"
color="gray"
{...form.getInputProps('type')}
/>
<Group position="right" mt="md">
<Button loading={submitLoading} type="submit">Add Filter</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 AddNewRegex;

View File

@@ -0,0 +1,120 @@
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch } from '@mantine/core';
import { useForm } from '@mantine/hooks';
import React, { useState } from 'react';
import { addservice, fireUpdateRequest, okNotify, startservice } from '../js/utils';
import { ImCross } from "react-icons/im"
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
},
validationRules:{
name: (value) => value !== ""?true:false,
port: (value) => value>0 && value<65536,
internalPort: (value) => value>0 && value<65536,
}
})
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)
addservice(chosenInternalPort?{ internalPort, name, port }:{ name, port }).then( res => {
if (res.status === "ok"){
setSubmitLoading(false)
close();
fireUpdateRequest();
if (autostart) startservice(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" />
<NumberInput
placeholder="8080"
min={1}
max={65535}
label="Public Service port"
{...form.getInputProps('port')}
/>
{form.values.chosenInternalPort?<>
<Space h="md" />
<NumberInput
placeholder="8080"
min={1}
max={65535}
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 position="right" mt="md">
<Button loading={submitLoading} type="submit">Add Service</Button>
</Group>
<Space h="md" />
{error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" /></>:null}
</form>
</Modal>
}
export default AddNewService;

View File

@@ -0,0 +1,13 @@
@use "../../vars" as *;
.box{
padding:30px;
margin:5px;
}
.regex_text{
padding: 10px;
background-color: $third_color;
border-radius: 15px;
}

View File

@@ -0,0 +1,114 @@
import { Grid, Text, Title, Badge, Space, ActionIcon, Tooltip } from '@mantine/core';
import React, { useState } from 'react';
import { RegexFilter } from '../../js/models';
import { activateregex, b64decode, deactivateregex, deleteregex, errorNotify, fireUpdateRequest, okNotify } from '../../js/utils';
import style from "./RegexView.module.scss";
import { BsTrashFill } from "react-icons/bs"
import YesNoModal from '../YesNoModal';
import FilterTypeSelector from '../FilterTypeSelector';
import { FaPause, FaPlay } from 'react-icons/fa';
function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
const mode_string = regexInfo.mode === "C"? "C -> S":
regexInfo.mode === "S"? "S -> C":
regexInfo.mode === "B"? "S <-> C": "🤔"
let regex_expr = b64decode(regexInfo.regex);
const [deleteModal, setDeleteModal] = useState(false);
const [deleteTooltipOpened, setDeleteTooltipOpened] = useState(false);
const [statusTooltipOpened, setStatusTooltipOpened] = useState(false);
const deleteRegex = () => {
deleteregex(regexInfo.id).then(res => {
if(!res){
okNotify(`Regex ${regex_expr} deleted successfully!`,`Regex '${regex_expr}' ID:${regexInfo.id} has been deleted!`)
fireUpdateRequest()
}else{
errorNotify(`Regex ${regex_expr} deleation failed!`,`Error: ${res}`)
}
}).catch( err => errorNotify(`Regex ${regex_expr} deleation failed!`,`Error: ${err}`))
}
const changeRegexStatus = () => {
(regexInfo.active?deactivateregex:activateregex)(regexInfo.id).then(res => {
if(!res){
okNotify(`Regex ${regex_expr} ${regexInfo.active?"deactivated":"activated"} successfully!`,`Regex '${regex_expr}' ID:${regexInfo.id} has been ${regexInfo.active?"deactivated":"activated"}!`)
fireUpdateRequest()
}else{
errorNotify(`Regex ${regex_expr} ${regexInfo.active?"deactivation":"activation"} failed!`,`Error: ${res}`)
}
}).catch( err => errorNotify(`Regex ${regex_expr} ${regexInfo.active?"deactivation":"activation"} failed!`,`Error: ${err}`))
}
return <div className={style.box}>
<Grid>
<Grid.Col span={2}>
<Title order={2} style={{color:"#FFF"}}>Regex:</Title>
</Grid.Col>
<Grid.Col span={8}>
<Text className={style.regex_text}> {regex_expr}</Text>
</Grid.Col>
<Grid.Col span={2} className='center-flex'>
<Space w="xs" />
<Tooltip label={regexInfo.active?"Deactivate":"Activate"} zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color={regexInfo.active?"orange":"teal"} opened={statusTooltipOpened}>
<ActionIcon color={regexInfo.active?"orange":"teal"} onClick={changeRegexStatus} size="xl" radius="md" variant="filled"
onFocus={() => setStatusTooltipOpened(false)} onBlur={() => setStatusTooltipOpened(false)}
onMouseEnter={() => setStatusTooltipOpened(true)} onMouseLeave={() => setStatusTooltipOpened(false)}
>{regexInfo.active?<FaPause size="20px" />:<FaPlay size="20px" />}</ActionIcon>
</Tooltip>
<Space w="xs" />
<Tooltip label="Delete regex" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="red" opened={deleteTooltipOpened} >
<ActionIcon color="red" onClick={()=>setDeleteModal(true)} size="xl" radius="md" variant="filled"
onFocus={() => setDeleteTooltipOpened(false)} onBlur={() => setDeleteTooltipOpened(false)}
onMouseEnter={() => setDeleteTooltipOpened(true)} onMouseLeave={() => setDeleteTooltipOpened(false)}
><BsTrashFill size={22} /></ActionIcon>
</Tooltip>
</Grid.Col>
<Grid.Col span={2} />
<Grid.Col className='center-flex-row' span={4}>
<Space h="xs" />
<FilterTypeSelector
size="md"
color="gray"
disabled
value={regexInfo.is_blacklist?"blacklist":"whitelist"}
/>
<Space h="md" />
<div className='center-flex'>
<Badge size="md" color="cyan" variant="filled">Service: {regexInfo.service_id}</Badge>
<Space w="xs" />
<Badge size="md" color={regexInfo.active?"lime":"red"} variant="filled">{regexInfo.active?"ACTIVE":"DISABLED"}</Badge>
<Space w="xs" />
<Badge size="md" color="gray" variant="filled">ID: {regexInfo.id}</Badge>
</div>
</Grid.Col>
<Grid.Col style={{width:"100%"}} span={6}>
<Space h="xs" />
<div className='center-flex-row'>
<Badge size="md" color={regexInfo.is_case_sensitive?"grape":"pink"} variant="filled">Case: {regexInfo.is_case_sensitive?"SENSIIVE":"INSENSITIVE"}</Badge>
<Space h="xs" />
<Badge size="md" color="yellow" variant="filled">Packets filtered: {regexInfo.n_packets}</Badge>
<Space h="xs" />
<Badge size="md" color="blue" variant="filled">Mode: {mode_string}</Badge>
</div>
</Grid.Col>
</Grid>
<YesNoModal
title='Are you sure to delete this regex?'
description={`You are going to delete the regex '${regex_expr}'.`}
onClose={()=>setDeleteModal(false)}
action={deleteRegex}
opened={deleteModal}
/>
</div>
}
export default RegexView;

View File

@@ -0,0 +1,107 @@
import { Button, Group, NumberInput, Space, Notification, Modal, Center, Title } from '@mantine/core';
import { useForm } from '@mantine/hooks';
import React, { useEffect, useState } from 'react';
import { changeports, fireUpdateRequest, okNotify } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { Service } from '../../js/models';
import { FaLongArrowAltDown } from 'react-icons/fa';
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
},
validationRules:{
internalPort: (value) => value>0 && value<65536,
port: (value) => value>0 && value<65536
}
})
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)
changeports(service.id, data).then( res => {
if (!res){
setSubmitLoading(false)
close();
fireUpdateRequest();
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)}>
<NumberInput
placeholder="30001"
min={1}
max={65535}
label="Internal Proxy Port"
{...form.getInputProps('internalPort')}
/>
<Space h="xl" />
<Center><FaLongArrowAltDown size={50}/></Center>
<NumberInput
placeholder="8080"
min={1}
max={65535}
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 position="right" mt="md">
<Button loading={submitLoading} disabled={
service.internal_port === form.values.internalPort && service.public_port === form.values.port
} type="submit">Change Port</Button>
</Group>
<Space h="md" />
{error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" /></>:null}
</form>
</Modal>
}
export default ChangePortModal;

View File

@@ -0,0 +1,69 @@
import { Button, Group, Space, TextInput, Notification, Modal } from '@mantine/core';
import { useForm } from '@mantine/hooks';
import React, { useEffect, useState } from 'react';
import { fireUpdateRequest, okNotify, renameservice } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { Service } from '../../js/models';
function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:Service }) {
const form = useForm({
initialValues: { name:service.name },
validationRules:{ name: (value) => value !== "" }
})
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)
renameservice(service.id, name).then( res => {
if (!res){
setSubmitLoading(false)
close();
fireUpdateRequest();
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 position="right" mt="md">
<Button loading={submitLoading} type="submit">Rename</Button>
</Group>
<Space h="md" />
{error?<>
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
Error: {error}
</Notification><Space h="md" /></>:null}
</form>
</Modal>
}
export default RenameForm;

View File

@@ -0,0 +1,18 @@
@use "../../index.scss" as *;
.row{
width: 95%;
padding: 30px 0px;
border-radius: 20px;
margin: 10px;
@extend .center-flex;
}
.name{
font-size: 2.3em;
font-weight: bolder;
margin-right: 10px;
margin-bottom: 13px;
color:#FFF;
}

View File

@@ -0,0 +1,234 @@
import { ActionIcon, Badge, Divider, Grid, MediaQuery, Menu, Space, Title, Tooltip } from '@mantine/core';
import React, { useState } from 'react';
import { FaPause, FaPlay, FaStop } from 'react-icons/fa';
import { Service } from '../../js/models';
import { MdOutlineArrowForwardIos } from "react-icons/md"
import style from "./ServiceRow.module.scss";
import YesNoModal from '../YesNoModal';
import { deleteservice, errorNotify, fireUpdateRequest, okNotify, pauseservice, regenport, startservice, stopservice } 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';
//"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 stopService = async () => {
setButtonLoading(true)
await stopservice(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} stopped successfully!`,`The service ${service.name} has been stopped!`)
fireUpdateRequest();
}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 startservice(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} started successfully!`,`The service ${service.name} has been started!`)
fireUpdateRequest();
}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 pauseservice(service.id).then(res => {
if(!res){
okNotify(`Service ${service.id} paused successfully!`,`The service ${service.name} has been paused (Transparent mode)!`)
fireUpdateRequest();
}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 = () => {
deleteservice(service.id).then(res => {
if (!res){
okNotify("Service delete complete!",`The service ${service.id} has been deleted!`)
fireUpdateRequest();
}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 = () => {
regenport(service.id).then(res => {
if (!res){
okNotify("Service port regeneration completed!",`The service ${service.id} has changed the internal port!`)
fireUpdateRequest();
}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={style.row} justify="flex-end" style={{width:"100%"}}>
<Grid.Col md={4} xs={12}>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}><div>
<div className="center-flex-row">
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.public_port}</Badge></div>
<Badge color={status_color} size="xl" radius="md">{service.internal_port} {"->"} {service.public_port}</Badge>
</div>
</div></MediaQuery>
<MediaQuery largerThan="md" styles={{ display: 'none' }}><div>
<div className="center-flex">
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.public_port}</Badge></div>
<Space w="xl" />
<Badge color={status_color} size="xl" radius="md">{service.internal_port} {"->"} {service.public_port}</Badge>
</div>
</div></MediaQuery>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<Space h="xl" />
</MediaQuery>
</Grid.Col>
<Grid.Col className="center-flex" md={8} xs={12}>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
<div className='flex-spacer' />
</MediaQuery>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<><Space w="xl" /><Space w="xl" /></>
</MediaQuery>
<div className="center-flex-row">
<Badge style={{marginBottom:"20px"}} color={status_color} radius="sm" size="xl" variant="filled">Status: <u>{service.status}</u></Badge>
<Badge style={{marginBottom:"8px"}}color="violet" radius="sm" size="lg" variant="filled">Regex: {service.n_regex}</Badge>
<Badge color="yellow" radius="sm" size="lg" variant="filled">Connections Blocked: {service.n_packets}</Badge>
</div>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<div className='flex-spacer' />
</MediaQuery>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
<><Space w="xl" /><Space w="xl" /></>
</MediaQuery>
<div className="center-flex">
<Menu>
<Menu.Label><b>Rename service</b></Menu.Label>
<Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
<Divider />
<Menu.Label><b>Change ports</b></Menu.Label>
<Menu.Item icon={<TbNumbers size={18} />} onClick={()=>setChoosePortModal(true)}>Change port</Menu.Item>
<Menu.Item icon={<BsArrowRepeat size={18} />} onClick={()=>setChangePortModal(true)}>Regen proxy port</Menu.Item>
<Divider />
<Menu.Label><b>Danger zone</b></Menu.Label>
<Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
</Menu>
<Space w="md"/>
{["pause","wait"].includes(service.status)?
<Tooltip label="Stop service" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="orange" opened={tooltipStopOpened} tooltipId="tooltip-stop-id">
<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} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color={service.status === "stop"?"cyan":"red"}>
<ActionIcon color={service.status === "stop"?"cyan":"red"} loading={buttonLoading}
onClick={pauseService} size="xl" radius="md" variant="filled"
/*disabled={service.status === "stop"}*/>
<FaPause size="20px" />
</ActionIcon>
</Tooltip>
}
<Space w="md"/>
<Tooltip label="Start service" transition="pop" zIndex={0} transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" 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>
</div>
<Space w="xl" /><Space w="xl" />
{onClick?<div>
<MdOutlineArrowForwardIos onClick={onClick} style={{cursor:"pointer"}} size={45} />
<Space w="xl" />
</div>:null}
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<><Space w="xl" /><Space w="xl" /></>
</MediaQuery>
</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}
/>
<hr style={{width:"100%"}}/>
<YesNoModal
title='Are you sure to delete this service?'
description={`You are going to delete the service '${service.id}', causing the stopping of the firewall and deleting all the regex associated. This will cause the shutdown of your service! ⚠️`}
onClose={()=>setDeleteModal(false) }
action={deleteService}
opened={deleteModal}
/>
<YesNoModal
title='Are you sure to change the proxy internal port?'
description={`You are going to change the proxy port '${service.internal_port}'. This will cause the shutdown of your service temporarily! ⚠️`}
onClose={()=>setChangePortModal(false)}
action={changePort}
opened={changePortModal}
/>
<ChangePortModal
service={service}
onClose={()=> setChoosePortModal(false)}
opened={choosePortModal}
/>
<RenameForm
onClose={()=>setRenameModal(false)}
opened={renameModal}
service={service}
/>
</>
}
export default ServiceRow;

View File

@@ -0,0 +1,116 @@
import { ServerResponse } from "../../js/models"
import { getapi, postapi } from "../../js/utils"
export type GeneralStats = {
services:number,
closed:number,
regexes:number
}
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 type RegexFilter = {
id:number,
service_id:string,
regex:string
is_blacklist:boolean,
is_case_sensitive:boolean,
mode:string //C S B => C->S S->C BOTH
n_packets:number,
active:boolean
}
export type RegexAddForm = {
service_id:string,
regex:string,
is_case_sensitive:boolean,
is_blacklist:boolean,
mode:string, // C->S S->C BOTH,
active: boolean
}
export const regexproxy = {
stats: async () => {
return await getapi("regexproxy/stats") as GeneralStats;
},
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[];
}
}

115
frontend/src/js/models.ts Executable file → Normal file
View File

@@ -1,82 +1,33 @@
export type GeneralStats = {
services:number,
closed:number,
regexes:number
}
export type Service = {
name:string,
service_id:string,
status:string,
port:number,
proto: string,
ip_int: string,
n_packets:number,
n_regex:number,
}
export type ServiceAddForm = {
name:string,
port:number,
proto:string,
ip_int:string,
}
export type ServiceAddResponse = {
status: string,
service_id?: string,
}
export type ServerResponse = {
status:string
}
export type ServerResponseToken = {
status:string,
access_token?:string
}
export type LoginResponse = {
status?:string,
access_token:string,
token_type:string
}
export type ServerStatusResponse = {
status:string,
loggined:boolean
}
export type PasswordSend = {
password:string,
}
export type ChangePassword = {
password:string,
expire:boolean
}
export type RegexFilter = {
id:number,
service_id:string,
regex:string
is_blacklist:boolean,
is_case_sensitive:boolean,
mode:string //C S B => C->S S->C BOTH
n_packets:number,
active:boolean
}
export type RegexAddForm = {
service_id:string,
regex:string,
is_case_sensitive:boolean,
is_blacklist:boolean,
mode:string, // C->S S->C BOTH,
active: boolean
}
export type IpInterface = {
name:string,
addr:string
}
export type ServerResponse = {
status:string
}
export type ServerResponseToken = {
status:string,
access_token?:string
}
export type LoginResponse = {
status?:string,
access_token:string,
token_type:string
}
export type ServerStatusResponse = {
status:string,
loggined:boolean
}
export type PasswordSend = {
password:string,
}
export type ChangePassword = {
password:string,
expire:boolean
}
export type IpInterface = {
name:string,
addr:string
}

View File

@@ -1,10 +1,11 @@
import { showNotification } from "@mantine/notifications";
import { ImCross } from "react-icons/im";
import { TiTick } from "react-icons/ti"
import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter, RegexAddForm, ServerStatusResponse, PasswordSend, ChangePassword, LoginResponse, ServerResponseToken, ServiceAddResponse, IpInterface } from "./models";
import { nfregex } from "../components/NFRegex/utils";
import { ChangePassword, IpInterface, LoginResponse, PasswordSend, ServerResponse, ServerResponseToken, ServerStatusResponse } from "./models";
var Buffer = require('buffer').Buffer
export const eventUpdateName = "update-info"
export const regex_ipv6 = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$";
@@ -50,6 +51,12 @@ export async function postapi(path:string,data:any,is_form:boolean=false):Promis
});
}
export function gatmainpath(){
const paths = window.location.pathname.split("/")
if (paths.length > 1) return paths[1]
return ""
}
export function fireUpdateRequest(){
window.dispatchEvent(new Event(eventUpdateName))
}
@@ -93,56 +100,6 @@ export async function login(data:PasswordSend) {
return status;
}
export const nfregex = {
stats: async () => {
return await getapi("nfregex/stats") as GeneralStats;
},
services: async () => {
return await getapi("nfregex/services") as Service[];
},
serviceinfo: async (service_id:string) => {
return await getapi(`nfregex/service/${service_id}`) as Service;
},
regexdelete: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/delete`) as ServerResponse;
return status === "ok"?undefined:status
},
regexenable: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/enable`) as ServerResponse;
return status === "ok"?undefined:status
},
regexdisable: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/disable`) as ServerResponse;
return status === "ok"?undefined:status
},
servicestart: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/start`) as ServerResponse;
return status === "ok"?undefined:status
},
servicerename: async (service_id:string, name: string) => {
const { status } = await postapi(`nfregex/service/${service_id}/rename`,{ name }) as ServerResponse;
return status === "ok"?undefined:status
},
servicestop: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/stop`) as ServerResponse;
return status === "ok"?undefined:status
},
servicesadd: async (data:ServiceAddForm) => {
return await postapi("nfregex/services/add",data) as ServiceAddResponse;
},
servicedelete: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/delete`) as ServerResponse;
return status === "ok"?undefined:status
},
regexesadd: async (data:RegexAddForm) => {
const { status } = await postapi("nfregex/regexes/add",data) as ServerResponse;
return status === "ok"?undefined:status
},
serviceregexes: async (service_id:string) => {
return await getapi(`nfregex/service/${service_id}/regexes`) as RegexFilter[];
}
}
export function errorNotify(title:string, description:string ){
showNotification({
autoClose: 2000,

View File

@@ -5,8 +5,8 @@ import RegexView from '../../components/NFRegex/RegexView';
import ServiceRow from '../../components/NFRegex/ServiceRow';
import AddNewRegex from '../../components/NFRegex/AddNewRegex';
import { BsPlusLg } from "react-icons/bs";
import { RegexFilter, Service } from '../../js/models';
import { errorNotify, eventUpdateName, fireUpdateRequest, nfregex } from '../../js/utils';
import { nfregex, RegexFilter, Service } from '../../components/NFRegex/utils';
import { errorNotify, eventUpdateName, fireUpdateRequest } from '../../js/utils';
import { useWindowEvent } from '@mantine/hooks';
function ServiceDetails() {

View File

@@ -3,10 +3,11 @@ import React, { useEffect, useState } from 'react';
import { BsPlusLg } from "react-icons/bs";
import { useNavigate, useParams } from 'react-router-dom';
import ServiceRow from '../../components/NFRegex/ServiceRow';
import { GeneralStats, Service } from '../../js/models';
import { errorNotify, eventUpdateName, fireUpdateRequest, nfregex } from '../../js/utils';
import { GeneralStats, nfregex, Service } from '../../components/NFRegex/utils';
import { errorNotify, eventUpdateName, fireUpdateRequest } from '../../js/utils';
import AddNewService from '../../components/NFRegex/AddNewService';
import { useWindowEvent } from '@mantine/hooks';
import AddNewRegex from '../../components/NFRegex/AddNewRegex';
function NFRegex({ children }: { children: any }) {
@@ -17,6 +18,7 @@ function NFRegex({ children }: { children: any }) {
const [open, setOpen] = useState(false);
const {srv} = useParams()
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
const [generalStats, setGeneralStats] = useState<GeneralStats>({closed:0, regexes:0, services:0});
@@ -43,16 +45,30 @@ function NFRegex({ children }: { children: any }) {
const closeModal = () => {setOpen(false);}
return <>
<Space h="sm" />
<div className='center-flex'>
<Title order={4}>Netfilter Regex</Title>
<div className='flex-spacer' />
<Badge size="sm" color="green" variant="filled">Services: {generalStats.services}</Badge>
<Space w="xs" />
<Badge size="sm" color="yellow" variant="filled">Filtered Connections: {generalStats.closed}</Badge>
<Space w="xs" />
<Badge size="sm" color="violet" variant="filled">Regexes: {generalStats.regexes}</Badge>
<Space w="xs" />
{ srv?
<Tooltip label="Add a new regex" position='bottom' transition="pop" transitionDuration={200} transitionTimingFunction="ease" 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' transition="pop" transitionDuration={200} transitionTimingFunction="ease" 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>
}
</div>
<div id="service-list" className="center-flex-row">
<Space h="sm" />
<div className='center-flex'>
<Badge color="green" size="lg" variant="filled">Services: {generalStats.services}</Badge>
<Space w="xs" />
<Badge size="lg" color="yellow" variant="filled">Filtered Connections: {generalStats.closed}</Badge>
<Space w="xs" />
<Badge size="lg" color="violet" variant="filled">Regexes: {generalStats.regexes}</Badge>
</div>
{srv?null:<>
<LoadingOverlay visible={loader} />
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.service_id} onClick={()=>{
@@ -60,7 +76,7 @@ function NFRegex({ children }: { children: any }) {
}} />):<><Space h="xl"/> <Title className='center-flex' align='center' order={3}>No services found! Add one clicking the "+" buttons</Title>
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
<div className='center-flex'>
<Tooltip label="Add a new service" transition="pop" transitionDuration={200} /*openDelay={500}*/ zIndex={0} transitionTimingFunction="ease" color="blue" opened={tooltipAddServOpened} tooltipId="tooltip-addServ-id">
<Tooltip label="Add a new service" transition="pop" transitionDuration={200} transitionTimingFunction="ease" 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>
@@ -71,6 +87,10 @@ function NFRegex({ children }: { children: any }) {
</>}
</div>
{srv?children:null}
{srv?
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
<AddNewService opened={open} onClose={closeModal} />
}
</>
}

View File

@@ -0,0 +1,53 @@
import { ActionIcon, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import React, { useEffect, useState } from 'react';
import { BsPlusLg } from "react-icons/bs";
import { useNavigate } from 'react-router-dom';
import ServiceRow from '../components/ServiceRow';
import { Service } from '../js/models';
import { errorNotify, eventUpdateName, fireUpdateRequest, servicelist } from '../js/utils';
import AddNewService from '../components/AddNewService';
import { useWindowEvent } from '@mantine/hooks';
function HomePage() {
const [services, setServices] = useState<Service[]>([]);
const [loader, setLoader] = useState(true);
const navigator = useNavigate()
const [open, setOpen] = useState(false);
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
const updateInfo = async () => {
await servicelist().then(res => {
setServices(res)
}).catch(err => {
errorNotify("Home Page Auto-Update failed!", err.toString())
})
setLoader(false)
}
useWindowEvent(eventUpdateName, updateInfo)
useEffect(fireUpdateRequest,[])
const closeModal = () => {setOpen(false);}
return <div id="service-list" className="center-flex-row">
<LoadingOverlay visible={loader} />
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.id} onClick={()=>{
navigator("/"+srv.id)
}} />):<><Space h="xl"/> <Title className='center-flex' align='center' order={3}>No services found! Add one clicking the "+" buttons</Title>
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
<div className='center-flex'>
<Tooltip label="Add a new service" transition="pop" transitionDuration={200} /*openDelay={500}*/ zIndex={0} transitionTimingFunction="ease" color="blue" opened={tooltipAddServOpened} tooltipId="tooltip-addServ-id">
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
aria-describedby="tooltip-addSrv-id"
onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)}
onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
</Tooltip>
</div>
</>}
<AddNewService opened={open} onClose={closeModal} />
</div>
}
export default HomePage;

View File

@@ -0,0 +1,84 @@
import { ActionIcon, Grid, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import React, { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import RegexView from '../components/RegexView';
import ServiceRow from '../components/ServiceRow';
import AddNewRegex from '../components/AddNewRegex';
import { BsPlusLg } from "react-icons/bs";
import { RegexFilter, Service } from '../js/models';
import { errorNotify, eventUpdateName, fireUpdateRequest, serviceinfo, serviceregexlist } from '../js/utils';
import { useWindowEvent } from '@mantine/hooks';
function ServiceDetails() {
const {srv_id} = useParams()
const [serviceInfo, setServiceInfo] = useState<Service>({
id:srv_id?srv_id:"",
internal_port:0,
n_packets:0,
n_regex:0,
name:srv_id?srv_id:"",
public_port:0,
status:"🤔"
})
const [regexesList, setRegexesList] = useState<RegexFilter[]>([])
const [loader, setLoader] = useState(true);
const [open, setOpen] = useState(false);
const closeModal = () => {setOpen(false);updateInfo();}
const updateInfo = async () => {
if (!srv_id) return
let error = false;
await serviceinfo(srv_id).then(res => {
setServiceInfo(res)
}).catch(
err =>{
error = true;
navigator("/")
})
if (error) return
await serviceregexlist(srv_id).then(res => {
setRegexesList(res)
}).catch(
err => errorNotify(`Updater for ${srv_id} service failed [Regex list]!`, err.toString())
)
setLoader(false)
}
useWindowEvent(eventUpdateName, updateInfo)
useEffect(fireUpdateRequest,[])
const navigator = useNavigate()
const [tooltipAddRegexOpened, setTooltipAddRegexOpened] = useState(false);
return <div>
<LoadingOverlay visible={loader} />
<ServiceRow service={serviceInfo} />
{regexesList.length === 0?<>
<Space h="xl" />
<Title className='center-flex' align='center' order={3}>No regex found for this service! Add one by clicking the "+" buttons</Title>
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
<div className='center-flex'>
<Tooltip label="Add a new regex" zIndex={0} transition="pop" transitionDuration={200} /*openDelay={500}*/ transitionTimingFunction="ease" color="blue" opened={tooltipAddRegexOpened} tooltipId="tooltip-AddRegex-id">
<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>
</div>
</>:
<Grid>
{regexesList.map( (regexInfo) => <Grid.Col key={regexInfo.id} lg={6} xs={12}><RegexView regexInfo={regexInfo} /></Grid.Col>)}
</Grid>
}
{srv_id?<AddNewRegex opened={open} onClose={closeModal} service={srv_id} />:null}
</div>
}
export default ServiceDetails;

View File

@@ -21,7 +21,7 @@ def puts(text, *args, color=colors.white, is_bold=False, **kwargs):
def sep(): puts("-----------------------------------", is_bold=True)
parser = argparse.ArgumentParser()
parser.add_argument('--port', "-p", type=int, required=False, help='Port where open the web service of the firewall', default=4444)
parser.add_argument('--thread-per-queue', "-t", type=int, required=False, help='Number of threads started for each queue', default=1)
parser.add_argument('--threads', "-t", type=int, required=False, help='Number of threads started for each service/utility', default=1)
parser.add_argument('--no-autostart', "-n", required=False, action="store_true", help='Auto-execute "docker-compose up -d --build"', default=False)
args = parser.parse_args()
@@ -30,8 +30,14 @@ puts(f"Firegex", color=colors.yellow, end="")
puts(" will start on port ", end="")
puts(f"{args.port}", color=colors.cyan)
if args.threads < 1:
puts("Insert a valid number of threads", color=colors.red)
exit()
os.chdir(os.path.dirname(os.path.realpath(__file__)))
gcc_params = f"-D MULTI_THREAD -D THREAD_NUM={args.threads}" if args.threads > 1 else ""
with open("docker-compose.yml","wt") as compose:
if "linux" in sys.platform and not 'microsoft-standard' in platform.uname().release: #Check if not is a wsl also
@@ -41,11 +47,14 @@ version: '3.9'
services:
firewall:
restart: unless-stopped
build: .
build:
context: .
args:
- GCC_PARAMS={gcc_params}
network_mode: "host"
environment:
- PORT={args.port}
- N_THREADS_NFQUEUE={args.thread_per_queue}
- NTHREADS={args.thread_per_queue}
volumes:
- /execute/db
cap_add:
@@ -62,12 +71,15 @@ version: '3.9'
services:
firewall:
restart: unless-stopped
build: .
build:
context: .
args:
- GCC_PARAMS={gcc_params}
ports:
- {args.port}:{args.port}
environment:
- PORT={args.port}
- N_THREADS_NFQUEUE={args.thread_per_queue}
- NTHREADS={args.thread_per_queue}
volumes:
- /execute/db
cap_add: