From ada3a7212b6e72df3826e1a57383b406e76d131f Mon Sep 17 00:00:00 2001 From: DomySh Date: Sun, 10 Jul 2022 15:05:56 +0200 Subject: [PATCH] Now on IPv6 --- backend/app.py | 107 +++++++++++------- backend/proxy.py | 105 +++++++++-------- backend/utils.py | 57 ++++++---- frontend/src/components/AddNewRegex.tsx | 6 +- frontend/src/components/AddNewService.tsx | 17 ++- frontend/src/components/Header/index.tsx | 7 +- frontend/src/components/RegexView/index.tsx | 2 +- .../src/components/ServiceRow/RenameForm.tsx | 2 +- frontend/src/components/ServiceRow/index.tsx | 11 +- frontend/src/js/models.ts | 12 +- frontend/src/js/utils.tsx | 28 ++--- frontend/src/pages/HomePage.tsx | 4 +- frontend/src/pages/ServiceDetails.tsx | 13 ++- 13 files changed, 208 insertions(+), 163 deletions(-) diff --git a/backend/app.py b/backend/app.py index 7230a3f..3b38e87 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,5 +1,6 @@ from base64 import b64decode -import sqlite3, uvicorn, sys, secrets, re, os, asyncio, httpx, urllib, websockets +import sqlite3, uvicorn, sys, secrets, re, os, asyncio +import httpx, urllib, websockets from typing import List, Union from fastapi import FastAPI, HTTPException, WebSocket, Depends from pydantic import BaseModel, BaseSettings @@ -19,12 +20,12 @@ db = SQLite('db/firegex.db') conf = KeyValueStorage(db) firewall = ProxyManager(db) - class Settings(BaseSettings): JWT_ALGORITHM: str = "HS256" REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/" REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html") VERSION = "1.3.0" + settings = Settings() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/login", auto_error=False) @@ -158,8 +159,10 @@ async def get_general_stats(auth: bool = Depends(is_loggined)): class ServiceModel(BaseModel): status: str + service_id: str port: int name: str + ipv6: bool n_regex: int n_packets: int @@ -167,66 +170,74 @@ class ServiceModel(BaseModel): async def get_service_list(auth: bool = Depends(is_loggined)): """Get the list of existent firegex services""" return db.query(""" - SELECT + SELECT + s.service_id service_id, s.status status, s.port port, s.name name, + s.ipv6 ipv6, COUNT(r.regex_id) n_regex, COALESCE(SUM(r.blocked_packets),0) n_packets - FROM services s LEFT JOIN regexes r ON r.service_port = s.port - GROUP BY s.port; + FROM services s LEFT JOIN regexes r + GROUP BY s.service_id; """) -@app.get('/api/service/{service_port}', response_model=ServiceModel) -async def get_service_by_id(service_port: int, auth: bool = Depends(is_loggined)): +@app.get('/api/service/{service_id}', response_model=ServiceModel) +async def get_service_by_id(service_id: str, auth: bool = Depends(is_loggined)): """Get info about a specific service using his id""" res = db.query(""" SELECT + s.service_id service_id, s.status status, s.port port, s.name name, + s.ipv6 ipv6, COUNT(r.regex_id) n_regex, COALESCE(SUM(r.blocked_packets),0) n_packets - FROM services s LEFT JOIN regexes r ON r.service_port = s.port WHERE s.port = ? - GROUP BY s.port; - """, service_port) + FROM services s LEFT JOIN regexes r 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] class StatusMessageModel(BaseModel): status:str -@app.get('/api/service/{service_port}/stop', response_model=StatusMessageModel) -async def service_stop(service_port: int, auth: bool = Depends(is_loggined)): +@app.get('/api/service/{service_id}/stop', response_model=StatusMessageModel) +async def service_stop(service_id: str, auth: bool = Depends(is_loggined)): """Request the stop of a specific service""" - await firewall.get(service_port).next(STATUS.STOP) + await firewall.get(service_id).next(STATUS.STOP) await refresh_frontend() return {'status': 'ok'} -@app.get('/api/service/{service_port}/start', response_model=StatusMessageModel) -async def service_start(service_port: int, auth: bool = Depends(is_loggined)): +@app.get('/api/service/{service_id}/start', response_model=StatusMessageModel) +async def service_start(service_id: str, auth: bool = Depends(is_loggined)): """Request the start of a specific service""" - await firewall.get(service_port).next(STATUS.ACTIVE) + await firewall.get(service_id).next(STATUS.ACTIVE) await refresh_frontend() return {'status': 'ok'} -@app.get('/api/service/{service_port}/delete', response_model=StatusMessageModel) -async def service_delete(service_port: int, auth: bool = Depends(is_loggined)): +@app.get('/api/service/{service_id}/delete', response_model=StatusMessageModel) +async def service_delete(service_id: str, auth: bool = Depends(is_loggined)): """Request the deletion of a specific service""" - db.query('DELETE FROM services WHERE port = ?;', service_port) - db.query('DELETE FROM regexes WHERE service_port = ?;', service_port) - await firewall.remove(service_port) + db.query('DELETE FROM services WHERE service_id = ?;', service_id) + db.query('DELETE FROM regexes WHERE service_id = ?;', service_id) + await firewall.remove(service_id) await refresh_frontend() return {'status': 'ok'} class RenameForm(BaseModel): name:str -@app.post('/api/service/{service_port}/rename', response_model=StatusMessageModel) -async def service_rename(service_port: int, form: RenameForm, auth: bool = Depends(is_loggined)): +@app.post('/api/service/{service_id}/rename', response_model=StatusMessageModel) +async def service_rename(service_id: str, form: RenameForm, auth: bool = Depends(is_loggined)): """Request to change the name of a specific service""" + form.name = refactor_name(form.name) if not form.name: return {'status': 'The name cannot be empty!'} - db.query('UPDATE services SET name=? WHERE port = ?;', form.name, service_port) + try: + db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id) + except sqlite3.IntegrityError: + return {'status': 'This name is already used'} await refresh_frontend() return {'status': 'ok'} @@ -234,28 +245,28 @@ class RegexModel(BaseModel): regex:str mode:str id:int - service_port:int + service_id:str is_blacklist: bool n_packets:int is_case_sensitive:bool active:bool -@app.get('/api/service/{service_port}/regexes', response_model=List[RegexModel]) -async def get_service_regexe_list(service_port: int, auth: bool = Depends(is_loggined)): +@app.get('/api/service/{service_id}/regexes', response_model=List[RegexModel]) +async def get_service_regexe_list(service_id: str, auth: bool = Depends(is_loggined)): """Get the list of the regexes of a service""" return db.query(""" SELECT - regex, mode, regex_id `id`, service_port, is_blacklist, + regex, mode, regex_id `id`, service_id, is_blacklist, blocked_packets n_packets, is_case_sensitive, active - FROM regexes WHERE service_port = ?; - """, service_port) + FROM regexes WHERE service_id = ?; + """, service_id) @app.get('/api/regex/{regex_id}', response_model=RegexModel) async def get_regex_by_id(regex_id: int, auth: bool = Depends(is_loggined)): """Get regex info using his id""" res = db.query(""" SELECT - regex, mode, regex_id `id`, service_port, is_blacklist, + regex, mode, regex_id `id`, service_id, is_blacklist, blocked_packets n_packets, is_case_sensitive, active FROM regexes WHERE `id` = ?; """, regex_id) @@ -268,7 +279,7 @@ async def regex_delete(regex_id: int, auth: bool = Depends(is_loggined)): 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_port"]).update_filters() + await firewall.get(res[0]["service_id"]).update_filters() await refresh_frontend() return {'status': 'ok'} @@ -279,7 +290,7 @@ async def regex_enable(regex_id: int, auth: bool = Depends(is_loggined)): 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_port"]).update_filters() + await firewall.get(res[0]["service_id"]).update_filters() await refresh_frontend() return {'status': 'ok'} @@ -289,12 +300,12 @@ async def regex_disable(regex_id: int, auth: bool = Depends(is_loggined)): 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_port"]).update_filters() + await firewall.get(res[0]["service_id"]).update_filters() await refresh_frontend() return {'status': 'ok'} class RegexAddForm(BaseModel): - service_port: int + service_id: str regex: str mode: str active: Union[bool,None] @@ -309,31 +320,39 @@ async def add_new_regex(form: RegexAddForm, auth: bool = Depends(is_loggined)): except Exception: return {"status":"Invalid regex"} try: - db.query("INSERT INTO regexes (service_port, regex, is_blacklist, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?, ?);", - form.service_port, form.regex, form.is_blacklist, form.mode, form.is_case_sensitive, True if form.active is None else form.active ) + 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_port).update_filters() + await firewall.get(form.service_id).update_filters() await refresh_frontend() return {'status': 'ok'} class ServiceAddForm(BaseModel): name: str port: int + ipv6: bool -@app.post('/api/services/add', response_model=StatusMessageModel) +class ServiceAddResponse(BaseModel): + status:str + service_id: Union[None,str] + +@app.post('/api/services/add', response_model=ServiceAddResponse) async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined)): """Add a new service""" + import time + srv_id = None try: - db.query("INSERT INTO services (name, port, status) VALUES (?, ?, ?)", - form.name, form.port, STATUS.STOP) + srv_id = str(form.port)+"::"+("ipv6" if form.ipv6 else "ipv4") + db.query("INSERT INTO services (service_id ,name, port, ipv6, status) VALUES (?, ?, ?, ?, ?)", + srv_id, refactor_name(form.name), form.port, form.ipv6, STATUS.STOP) except sqlite3.IntegrityError: - return {'status': 'Name or/and ports of the service has been already assigned to another service'} + return {'status': 'Name or/and ports of the service has been already assigned'} await firewall.reload() + init_t = time.time() await refresh_frontend() - - return {'status': 'ok'} + return {'status': 'ok', 'service_id': srv_id} async def frontend_debug_proxy(path): httpc = httpx.AsyncClient() diff --git a/backend/proxy.py b/backend/proxy.py index 27466cc..967ace6 100755 --- a/backend/proxy.py +++ b/backend/proxy.py @@ -1,24 +1,25 @@ -import multiprocessing from threading import Thread from typing import List from netfilterqueue import NetfilterQueue from multiprocessing import Manager, Process -from scapy.all import IP, TCP, UDP from subprocess import Popen, PIPE import os, traceback, pcre, re QUEUE_BASE_NUM = 1000 -def bind_queues(func, len_list=1): +def bind_queues(func, ipv6, len_list=1): + from scapy.all import IP, TCP, UDP, IPv6 if len_list <= 0: raise Exception("len must be >= 1") queue_list = [] starts = QUEUE_BASE_NUM end = starts - def func_wrap(pkt): - pkt_parsed = IP(pkt.get_payload()) + pkt_parsed = IPv6(pkt.get_payload()) if ipv6 else IP(pkt.get_payload()) try: - if pkt_parsed[UDP if UDP in pkt_parsed else TCP].payload: func(pkt, pkt_parsed) + payload = None + if UDP in pkt_parsed: payload = pkt_parsed[UDP].payload + if TCP in pkt_parsed: payload = pkt_parsed[TCP].payload + if payload: func(pkt, pkt_parsed, bytes(payload)) else: pkt.accept() except Exception: traceback.print_exc() @@ -52,15 +53,16 @@ class ProtoTypes: class IPTables: - @staticmethod - def command(params): + def __init__(self, ipv6=False): + self.ipv6 = ipv6 + + def command(self, params): if os.geteuid() != 0: exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.") - return Popen(["iptables"]+params, stdout=PIPE, stderr=PIPE).communicate() + return Popen(["ip6tables"]+params if self.ipv6 else ["iptables"]+params, stdout=PIPE, stderr=PIPE).communicate() - @staticmethod - def list_filters(param): - stdout, strerr = IPTables.command(["-L", str(param), "--line-number", "-n"]) + def list_filters(self, param): + stdout, strerr = self.command(["-L", str(param), "--line-number", "-n"]) output = [ele.split() for ele in stdout.decode().split("\n")] return [{ "id": ele[0], @@ -72,42 +74,35 @@ class IPTables: "details": " ".join(ele[6:]) if len(ele) >= 7 else "", } for ele in output if len(ele) >= 6 and ele[0].isnumeric()] - @staticmethod - def delete_command(param, id): - IPTables.command(["-R", str(param), str(id)]) + def delete_command(self, param, id): + self.command(["-R", str(param), str(id)]) - @staticmethod - def create_chain(name): - IPTables.command(["-N", str(name)]) + def create_chain(self, name): + self.command(["-N", str(name)]) - @staticmethod - def flush_chain(name): - IPTables.command(["-F", str(name)]) + def flush_chain(self, name): + self.command(["-F", str(name)]) - @staticmethod - def add_chain_to_input(name): - IPTables.command(["-I", "INPUT", "-j", str(name)]) + def add_chain_to_input(self, name): + self.command(["-I", "INPUT", "-j", str(name)]) - @staticmethod - def add_chain_to_output(name): - IPTables.command(["-I", "OUTPUT", "-j", str(name)]) + def add_chain_to_output(self, name): + self.command(["-I", "OUTPUT", "-j", str(name)]) - @staticmethod - def add_s_to_c(proto, port, queue_range): + def add_s_to_c(self, proto, port, queue_range): init, end = queue_range if init > end: init, end = end, init - IPTables.command([ + self.command([ "-A", FilterTypes.OUTPUT, "-p", str(proto), "--sport", str(port), "-j", "NFQUEUE", "--queue-num" if init == end else "--queue-balance", f"{init}" if init == end else f"{init}:{end}", "--queue-bypass" ]) - @staticmethod - def add_c_to_s(proto, port, queue_range): + def add_c_to_s(self, proto, port, queue_range): init, end = queue_range if init > end: init, end = end, init - IPTables.command([ + self.command([ "-A", FilterTypes.INPUT, "-p", str(proto), "--dport", str(port), "-j", "NFQUEUE", "--queue-num" if init == end else "--queue-balance", @@ -115,41 +110,45 @@ class IPTables: ]) class FiregexFilter(): - def __init__(self, type, number, queue, proto, port): + def __init__(self, type, number, queue, proto, port, ipv6): self.type = type self.id = int(number) self.queue = queue self.proto = proto self.port = int(port) + self.iptable = IPTables(ipv6) + def __repr__(self) -> str: return f"" def delete(self): - IPTables.delete_command(self.type, self.id) + self.iptable.delete_command(self.type, self.id) class FiregexFilterManager: - def __init__(self): - IPTables.create_chain(FilterTypes.INPUT) - IPTables.create_chain(FilterTypes.OUTPUT) + def __init__(self, ipv6): + self.ipv6 = ipv6 + self.iptables = IPTables(ipv6) + self.iptables.create_chain(FilterTypes.INPUT) + self.iptables.create_chain(FilterTypes.OUTPUT) input_found = False output_found = False - for filter in IPTables.list_filters("INPUT"): + for filter in self.iptables.list_filters("INPUT"): if filter["target"] == FilterTypes.INPUT: input_found = True break - for filter in IPTables.list_filters("OUTPUT"): + for filter in self.iptables.list_filters("OUTPUT"): if filter["target"] == FilterTypes.OUTPUT: output_found = True break - if not input_found: IPTables.add_chain_to_input(FilterTypes.INPUT) - if not output_found: IPTables.add_chain_to_output(FilterTypes.OUTPUT) + if not input_found: self.iptables.add_chain_to_input(FilterTypes.INPUT) + if not output_found: self.iptables.add_chain_to_output(FilterTypes.OUTPUT) def get(self) -> List[FiregexFilter]: res = [] for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]: - for filter in IPTables.list_filters(filter_type): + for filter in self.iptables.list_filters(filter_type): queue_num = None balanced = re.findall(r"NFQUEUE balance ([0-9]+):([0-9]+)", filter["details"]) numbered = re.findall(r"NFQUEUE num ([0-9]+)", filter["details"]) @@ -162,7 +161,8 @@ class FiregexFilterManager: number=filter["id"], queue=queue_num, proto=filter["prot"], - port=int(port[0]) + port=int(port[0]), + ipv6=self.ipv6 )) return res @@ -170,18 +170,18 @@ class FiregexFilterManager: for ele in self.get(): if int(port) == ele.port: return None - def c_to_s(pkt, data): return func(pkt, data, True) - def s_to_c(pkt, data): return func(pkt, data, False) + def c_to_s(pkt, data, payload): return func(pkt, data, payload, True) + def s_to_c(pkt, data, payload): return func(pkt, data, payload, False) queues_c_to_s, codes = bind_queues(c_to_s, n_threads) - IPTables.add_c_to_s(proto, port, codes) + self.iptables.add_c_to_s(proto, port, codes) queues_s_to_c, codes = bind_queues(s_to_c, n_threads) - IPTables.add_s_to_c(proto, port, codes) + self.iptables.add_s_to_c(proto, port, codes) return queues_c_to_s + queues_s_to_c def delete_all(self): for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]: - IPTables.flush_chain(filter_type) + self.iptables.flush_chain(filter_type) def delete_by_port(self, port): for filter in self.get(): @@ -209,8 +209,8 @@ class Filter: return True if self.compiled_regex.search(data) else False class Proxy: - def __init__(self, port, filters=None): - self.manager = FiregexFilterManager() + def __init__(self, port, ipv6, filters=None): + self.manager = FiregexFilterManager(ipv6) self.port = port self.filters = Manager().list(filters) if filters else Manager().list([]) self.process = None @@ -224,8 +224,7 @@ class Proxy: def _starter(self): self.manager.delete_by_port(self.port) - def regex_filter(pkt, data, by_client): - packet = bytes(data[TCP if TCP in data else UDP].payload) + def regex_filter(pkt, data, packet, by_client): try: for i, filter in enumerate(self.filters): if (by_client and filter.c_to_s) or (not by_client and filter.s_to_c): diff --git a/backend/utils.py b/backend/utils.py index f5763dd..9ad2075 100755 --- a/backend/utils.py +++ b/backend/utils.py @@ -49,27 +49,30 @@ class SQLite(): self.connect() self.create_schema({ 'services': { + 'service_id': 'VARCHAR(100) PRIMARY KEY', 'status': 'VARCHAR(100) NOT NULL', - 'port': 'INT NOT NULL CHECK(port > 0 and port < 65536) UNIQUE PRIMARY KEY', - 'name': 'VARCHAR(100) NOT NULL' + 'port': 'INT NOT NULL CHECK(port > 0 and port < 65536)', + 'name': 'VARCHAR(100) NOT NULL UNIQUE', + 'ipv6': 'BOOLEAN NOT NULL CHECK (ipv6 IN (0, 1)) DEFAULT 0', }, 'regexes': { 'regex': 'TEXT NOT NULL', 'mode': 'VARCHAR(1) NOT NULL', - 'service_port': 'INT 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_port)':'REFERENCES services (port)', + 'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1)) DEFAULT 1', + 'FOREIGN KEY (service_id)':'REFERENCES services (service_id)', }, 'keys_values': { 'key': 'VARCHAR(100) PRIMARY KEY', 'value': 'VARCHAR(100) NOT NULL', }, }) - self.query("CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_port,is_blacklist,mode,is_case_sensitive);") + self.query("CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (ipv6,port);") + self.query("CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);") class KeyValueStorage: def __init__(self, db): @@ -95,10 +98,11 @@ class STATUS: class ServiceNotFoundException(Exception): pass class ServiceManager: - def __init__(self, port, db): + def __init__(self, id, port, ipv6, db): + self.id = id self.port = port self.db = db - self.proxy = Proxy(port) + self.proxy = Proxy(port, ipv6) self.status = STATUS.STOP self.filters = {} self._update_filters_from_db() @@ -110,8 +114,8 @@ class ServiceManager: SELECT regex, mode, regex_id `id`, is_blacklist, blocked_packets n_packets, is_case_sensitive - FROM regexes WHERE service_port = ? AND active=1; - """, self.port) + FROM regexes WHERE service_id = ? AND active=1; + """, self.id) #Filter check old_filters = set(self.filters.keys()) @@ -137,7 +141,7 @@ class ServiceManager: self.proxy.set_filters(self.filters.values()) def __update_status_db(self, status): - self.db.query("UPDATE services SET status = ? WHERE port = ?;", status, self.port) + self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.id) async def next(self,to): async with self.lock: @@ -190,11 +194,11 @@ class ProxyManager: for key in list(self.proxy_table.keys()): await self.remove(key) - async def remove(self,port): + async def remove(self,srv_id): async with self.lock: - if port in self.proxy_table: - await self.proxy_table[port].next(STATUS.STOP) - del self.proxy_table[port] + if srv_id in self.proxy_table: + await self.proxy_table[srv_id].next(STATUS.STOP) + del self.proxy_table[srv_id] async def init(self, callback = None): self.init_updater(callback) @@ -202,13 +206,13 @@ class ProxyManager: async def reload(self): async with self.lock: - for srv in self.db.query('SELECT port, status FROM services;'): - srv_port, req_status = srv["port"], srv["status"] + for srv in self.db.query('SELECT service_id, port, status, ipv6 FROM services;'): + srv_id, srv_port, req_status, srv_ipv6 = srv["service_id"], srv["port"], srv["status"], srv["ipv6"] if srv_port in self.proxy_table: continue - self.proxy_table[srv_port] = ServiceManager(srv_port,self.db) - await self.proxy_table[srv_port].next(req_status) + self.proxy_table[srv_id] = ServiceManager(srv_id, srv_port, srv_ipv6, self.db) + await self.proxy_table[srv_id].next(req_status) async def _stats_updater(self, callback): try: @@ -226,10 +230,13 @@ class ProxyManager: self.updater_task = None return - - - def get(self,port): - if port in self.proxy_table: - return self.proxy_table[port] + def get(self,srv_id): + if srv_id in self.proxy_table: + return self.proxy_table[srv_id] else: - raise ServiceNotFoundException() \ No newline at end of file + raise ServiceNotFoundException() + +def refactor_name(name:str): + name = name.strip() + while " " in name: name = name.replace(" "," ") + return name \ No newline at end of file diff --git a/frontend/src/components/AddNewRegex.tsx b/frontend/src/components/AddNewRegex.tsx index 099ef16..01a72f0 100755 --- a/frontend/src/components/AddNewRegex.tsx +++ b/frontend/src/components/AddNewRegex.tsx @@ -15,7 +15,7 @@ type RegexAddInfo = { deactive:boolean } -function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:number }) { +function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:string }) { const form = useForm({ initialValues: { @@ -48,7 +48,7 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=> const request:RegexAddForm = { is_blacklist:values.type !== "whitelist", is_case_sensitive: !values.is_case_insensitive, - service_port: service, + service_id: service, mode: filter_mode?filter_mode:"B", regex: b64encode(values.regex), active: !values.deactive @@ -58,7 +58,7 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=> if (!res){ setSubmitLoading(false) close(); - okNotify(`Regex ${b64decode(request.regex)} has been added`, `Successfully added ${request.is_case_sensitive?"case sensitive":"case insensitive"} ${request.is_blacklist?"blacklist":"whitelist"} regex to ${request.service_port} service`) + 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") diff --git a/frontend/src/components/AddNewService.tsx b/frontend/src/components/AddNewService.tsx index 22b999d..b086be3 100755 --- a/frontend/src/components/AddNewService.tsx +++ b/frontend/src/components/AddNewService.tsx @@ -7,6 +7,7 @@ import { ImCross } from "react-icons/im" type ServiceAddForm = { name:string, port:number, + ipv6:boolean, autostart: boolean, } @@ -16,6 +17,7 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) initialValues: { name:"", port:8080, + ipv6:false, autostart: true }, validationRules:{ @@ -33,13 +35,13 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) const [submitLoading, setSubmitLoading] = useState(false) const [error, setError] = useState(null) - const submitRequest = ({ name, port, autostart }:ServiceAddForm) =>{ + const submitRequest = ({ name, port, autostart, ipv6 }:ServiceAddForm) =>{ setSubmitLoading(true) - addservice({name, port}).then( res => { - if (res.status === "ok"){ + addservice({name, port, ipv6}).then( res => { + if (res.status === "ok" && res.service_id){ setSubmitLoading(false) close(); - if (autostart) startservice(port) + if (autostart) startservice(res.service_id) okNotify(`Service ${name} has been added`, `Successfully added service with port ${port}`) }else{ setSubmitLoading(false) @@ -76,6 +78,13 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {...form.getInputProps('autostart', { type: 'checkbox' })} /> + + + + diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index 6760523..d794750 100755 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -70,7 +70,6 @@ function Header() { } const {srv} = useParams() - const service_port = srv?parseInt(srv):null const [open, setOpen] = useState(false); const closeModal = () => {setOpen(false);} @@ -125,7 +124,7 @@ function Header() { :null} - { service_port? + { srv? setOpen(true)} size="xl" radius="md" variant="filled" aria-describedby="tooltip-add-id" @@ -140,8 +139,8 @@ function Header() { } - {service_port? - : + {srv? + : } setChangePasswordModal(false)} closeOnClickOutside={false} centered> diff --git a/frontend/src/components/RegexView/index.tsx b/frontend/src/components/RegexView/index.tsx index 82da7b5..3a1242e 100755 --- a/frontend/src/components/RegexView/index.tsx +++ b/frontend/src/components/RegexView/index.tsx @@ -79,7 +79,7 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) { />
- Service: {regexInfo.service_port} + Service: {regexInfo.service_id} {regexInfo.active?"ACTIVE":"DISABLED"} diff --git a/frontend/src/components/ServiceRow/RenameForm.tsx b/frontend/src/components/ServiceRow/RenameForm.tsx index ca15dd3..b02b0b2 100644 --- a/frontend/src/components/ServiceRow/RenameForm.tsx +++ b/frontend/src/components/ServiceRow/RenameForm.tsx @@ -25,7 +25,7 @@ function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>v const submitRequest = ({ name }:{ name:string }) => { setSubmitLoading(true) - renameservice(service.port, name).then( res => { + renameservice(service.service_id, name).then( res => { if (!res){ setSubmitLoading(false) close(); diff --git a/frontend/src/components/ServiceRow/index.tsx b/frontend/src/components/ServiceRow/index.tsx index e8e2704..2ef2d90 100755 --- a/frontend/src/components/ServiceRow/index.tsx +++ b/frontend/src/components/ServiceRow/index.tsx @@ -25,7 +25,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) const stopService = async () => { setButtonLoading(true) - await stopservice(service.port).then(res => { + await stopservice(service.service_id).then(res => { if(!res){ okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.port} has been stopped!`) }else{ @@ -39,7 +39,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) const startService = async () => { setButtonLoading(true) - await startservice(service.port).then(res => { + await startservice(service.service_id).then(res => { if(!res){ okNotify(`Service ${service.name} started successfully!`,`The service on ${service.port} has been started!`) }else{ @@ -52,7 +52,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) } const deleteService = () => { - deleteservice(service.port).then(res => { + deleteservice(service.service_id).then(res => { if (!res){ okNotify("Service delete complete!",`The service ${service.name} has been deleted!`) }else @@ -94,8 +94,11 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
- Connections Blocked: {service.n_packets} + Connections Blocked: {service.n_packets} + Regex: {service.n_regex} + + Protocol: {service.ipv6?"IPv6":"IPv4"}
diff --git a/frontend/src/js/models.ts b/frontend/src/js/models.ts index fb9c458..6354516 100755 --- a/frontend/src/js/models.ts +++ b/frontend/src/js/models.ts @@ -6,8 +6,10 @@ export type GeneralStats = { export type Service = { name:string, + service_id:string, status:string, port:number, + ipv6:boolean, n_packets:number, n_regex:number, } @@ -15,9 +17,15 @@ export type Service = { export type ServiceAddForm = { name:string, port:number, + ipv6:boolean, internalPort?:number } +export type ServiceAddResponse = { + status: string, + service_id?: string, +} + export type ServerResponse = { status:string } @@ -49,7 +57,7 @@ export type ChangePassword = { export type RegexFilter = { id:number, - service_port:number, + service_id:string, regex:string is_blacklist:boolean, is_case_sensitive:boolean, @@ -59,7 +67,7 @@ export type RegexFilter = { } export type RegexAddForm = { - service_port:number, + service_id:string, regex:string, is_case_sensitive:boolean, is_blacklist:boolean, diff --git a/frontend/src/js/utils.tsx b/frontend/src/js/utils.tsx index 094da71..2cce35f 100755 --- a/frontend/src/js/utils.tsx +++ b/frontend/src/js/utils.tsx @@ -1,7 +1,7 @@ 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 } from "./models"; +import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter, RegexAddForm, ServerStatusResponse, PasswordSend, ChangePassword, LoginResponse, ServerResponseToken, ServiceAddResponse } from "./models"; var Buffer = require('buffer').Buffer @@ -64,8 +64,8 @@ export async function servicelist(){ return await getapi("services") as Service[]; } -export async function serviceinfo(service_port:number){ - return await getapi(`service/${service_port}`) as Service; +export async function serviceinfo(service_id:string){ + return await getapi(`service/${service_id}`) as Service; } export async function logout(){ @@ -109,27 +109,27 @@ export async function deactivateregex(regex_id:number){ return status === "ok"?undefined:status } -export async function startservice(service_port:number){ - const { status } = await getapi(`service/${service_port}/start`) as ServerResponse; +export async function startservice(service_id:string){ + const { status } = await getapi(`service/${service_id}/start`) as ServerResponse; return status === "ok"?undefined:status } -export async function renameservice(service_port:number, name: string){ - const { status } = await postapi(`service/${service_port}/rename`,{ name }) as ServerResponse; +export async function renameservice(service_id:string, name: string){ + const { status } = await postapi(`service/${service_id}/rename`,{ name }) as ServerResponse; return status === "ok"?undefined:status } -export async function stopservice(service_port:number){ - const { status } = await getapi(`service/${service_port}/stop`) as ServerResponse; +export async function stopservice(service_id:string){ + const { status } = await getapi(`service/${service_id}/stop`) as ServerResponse; return status === "ok"?undefined:status } export async function addservice(data:ServiceAddForm) { - return await postapi("services/add",data) as ServerResponse; + return await postapi("services/add",data) as ServiceAddResponse; } -export async function deleteservice(service_port:number) { - const { status } = await getapi(`service/${service_port}/delete`) as ServerResponse; +export async function deleteservice(service_id:string) { + const { status } = await getapi(`service/${service_id}/delete`) as ServerResponse; return status === "ok"?undefined:status } @@ -139,8 +139,8 @@ export async function addregex(data:RegexAddForm) { return status === "ok"?undefined:status } -export async function serviceregexlist(service_port:number){ - return await getapi(`service/${service_port}/regexes`) as RegexFilter[]; +export async function serviceregexlist(service_id:string){ + return await getapi(`service/${service_id}/regexes`) as RegexFilter[]; } diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 385c5d0..835d623 100755 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -33,8 +33,8 @@ function HomePage() { return
- {services.length > 0?services.map( srv => { - navigator("/"+srv.port) + {services.length > 0?services.map( srv => { + navigator("/"+srv.service_id) }} />):<> No services found! Add one clicking the "+" buttons
diff --git a/frontend/src/pages/ServiceDetails.tsx b/frontend/src/pages/ServiceDetails.tsx index d60d628..3b50f18 100755 --- a/frontend/src/pages/ServiceDetails.tsx +++ b/frontend/src/pages/ServiceDetails.tsx @@ -11,12 +11,13 @@ import { useWindowEvent } from '@mantine/hooks'; function ServiceDetails() { const {srv} = useParams() - const service_port = srv?parseInt(srv):null const [serviceInfo, setServiceInfo] = useState({ + service_id: "", port:0, n_packets:0, n_regex:0, name:"", + ipv6:false, status:"🤔" }) @@ -26,9 +27,9 @@ function ServiceDetails() { const closeModal = () => {setOpen(false);updateInfo();} const updateInfo = async () => { - if (!service_port) return + if (!srv) return let error = false; - await serviceinfo(service_port).then(res => { + await serviceinfo(srv).then(res => { setServiceInfo(res) }).catch( err =>{ @@ -36,10 +37,10 @@ function ServiceDetails() { navigator("/") }) if (error) return - await serviceregexlist(service_port).then(res => { + await serviceregexlist(srv).then(res => { setRegexesList(res) }).catch( - err => errorNotify(`Updater for ${service_port} service failed [Regex list]!`, err.toString()) + err => errorNotify(`Updater for ${srv} service failed [Regex list]!`, err.toString()) ) setLoader(false) } @@ -73,7 +74,7 @@ function ServiceDetails() { } - {service_port?:null} + {srv?:null}