Now on IPv6

This commit is contained in:
DomySh
2022-07-10 15:05:56 +02:00
parent 8cd1b69752
commit ada3a7212b
13 changed files with 208 additions and 163 deletions

View File

@@ -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()

View File

@@ -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"<FiregexFilter type={self.type} id={self.id} port={self.port} proto={self.proto} queue={self.queue}>"
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):

View File

@@ -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()
raise ServiceNotFoundException()
def refactor_name(name:str):
name = name.strip()
while " " in name: name = name.replace(" "," ")
return name

View File

@@ -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")

View File

@@ -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<string|null>(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' })}
/>
<Space h="sm" />
<Switch
label="Filter on Ipv6"
{...form.getInputProps('ipv6', { type: 'checkbox' })}
/>
<Group position="right" mt="md">
<Button loading={submitLoading} type="submit">Add Service</Button>
</Group>

View File

@@ -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() {
</ActionIcon>
</Tooltip>
:null}
{ service_port?
{ 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"
@@ -140,8 +139,8 @@ function Header() {
</Tooltip>
}
{service_port?
<AddNewRegex opened={open} onClose={closeModal} service={service_port} />:
{srv?
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
<AddNewService opened={open} onClose={closeModal} />
}
<Modal size="xl" title="Change Firewall Password" opened={changePasswordModal} onClose={()=>setChangePasswordModal(false)} closeOnClickOutside={false} centered>

View File

@@ -79,7 +79,7 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
/>
<Space h="md" />
<div className='center-flex'>
<Badge size="md" color="cyan" variant="filled">Service: {regexInfo.service_port}</Badge>
<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" />

View File

@@ -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();

View File

@@ -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 })
</MediaQuery>
<div className="center-flex-row">
<Badge style={{marginBottom:"8px"}} color="yellow" radius="sm" size="md" variant="filled">Connections Blocked: {service.n_packets}</Badge>
<Badge color="yellow" radius="sm" size="md" variant="filled">Connections Blocked: {service.n_packets}</Badge>
<Space h="xs" />
<Badge color="violet" radius="sm" size="md" variant="filled">Regex: {service.n_regex}</Badge>
<Space h="xs" />
<Badge color={service.ipv6?"pink":"cyan"} radius="sm" size="md" variant="filled">Protocol: {service.ipv6?"IPv6":"IPv4"}</Badge>
</div>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<div className='flex-spacer' />

View File

@@ -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,

View File

@@ -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[];
}

View File

@@ -33,8 +33,8 @@ function HomePage() {
return <div id="service-list" className="center-flex-row">
<LoadingOverlay visible={loader} />
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.port} onClick={()=>{
navigator("/"+srv.port)
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.service_id} onClick={()=>{
navigator("/"+srv.service_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'>

View File

@@ -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>({
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() {
</Grid>
}
{service_port?<AddNewRegex opened={open} onClose={closeModal} service={service_port} />:null}
{srv?<AddNewRegex opened={open} onClose={closeModal} service={srv} />:null}
</div>