Now on IPv6
This commit is contained in:
105
backend/app.py
105
backend/app.py
@@ -1,5 +1,6 @@
|
|||||||
from base64 import b64decode
|
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 typing import List, Union
|
||||||
from fastapi import FastAPI, HTTPException, WebSocket, Depends
|
from fastapi import FastAPI, HTTPException, WebSocket, Depends
|
||||||
from pydantic import BaseModel, BaseSettings
|
from pydantic import BaseModel, BaseSettings
|
||||||
@@ -19,13 +20,13 @@ db = SQLite('db/firegex.db')
|
|||||||
conf = KeyValueStorage(db)
|
conf = KeyValueStorage(db)
|
||||||
firewall = ProxyManager(db)
|
firewall = ProxyManager(db)
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
JWT_ALGORITHM: str = "HS256"
|
JWT_ALGORITHM: str = "HS256"
|
||||||
REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/"
|
REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/"
|
||||||
REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html")
|
REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html")
|
||||||
VERSION = "1.3.0"
|
VERSION = "1.3.0"
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/login", auto_error=False)
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/login", auto_error=False)
|
||||||
crypto = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
crypto = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
@@ -158,8 +159,10 @@ async def get_general_stats(auth: bool = Depends(is_loggined)):
|
|||||||
|
|
||||||
class ServiceModel(BaseModel):
|
class ServiceModel(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
|
service_id: str
|
||||||
port: int
|
port: int
|
||||||
name: str
|
name: str
|
||||||
|
ipv6: bool
|
||||||
n_regex: int
|
n_regex: int
|
||||||
n_packets: int
|
n_packets: int
|
||||||
|
|
||||||
@@ -168,65 +171,73 @@ async def get_service_list(auth: bool = Depends(is_loggined)):
|
|||||||
"""Get the list of existent firegex services"""
|
"""Get the list of existent firegex services"""
|
||||||
return db.query("""
|
return db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
|
s.service_id service_id,
|
||||||
s.status status,
|
s.status status,
|
||||||
s.port port,
|
s.port port,
|
||||||
s.name name,
|
s.name name,
|
||||||
|
s.ipv6 ipv6,
|
||||||
COUNT(r.regex_id) n_regex,
|
COUNT(r.regex_id) n_regex,
|
||||||
COALESCE(SUM(r.blocked_packets),0) n_packets
|
COALESCE(SUM(r.blocked_packets),0) n_packets
|
||||||
FROM services s LEFT JOIN regexes r ON r.service_port = s.port
|
FROM services s LEFT JOIN regexes r
|
||||||
GROUP BY s.port;
|
GROUP BY s.service_id;
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@app.get('/api/service/{service_port}', response_model=ServiceModel)
|
@app.get('/api/service/{service_id}', response_model=ServiceModel)
|
||||||
async def get_service_by_id(service_port: int, auth: bool = Depends(is_loggined)):
|
async def get_service_by_id(service_id: str, auth: bool = Depends(is_loggined)):
|
||||||
"""Get info about a specific service using his id"""
|
"""Get info about a specific service using his id"""
|
||||||
res = db.query("""
|
res = db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
|
s.service_id service_id,
|
||||||
s.status status,
|
s.status status,
|
||||||
s.port port,
|
s.port port,
|
||||||
s.name name,
|
s.name name,
|
||||||
|
s.ipv6 ipv6,
|
||||||
COUNT(r.regex_id) n_regex,
|
COUNT(r.regex_id) n_regex,
|
||||||
COALESCE(SUM(r.blocked_packets),0) n_packets
|
COALESCE(SUM(r.blocked_packets),0) n_packets
|
||||||
FROM services s LEFT JOIN regexes r ON r.service_port = s.port WHERE s.port = ?
|
FROM services s LEFT JOIN regexes r WHERE s.service_id = ?
|
||||||
GROUP BY s.port;
|
GROUP BY s.service_id;
|
||||||
""", service_port)
|
""", service_id)
|
||||||
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
|
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
|
||||||
return res[0]
|
return res[0]
|
||||||
|
|
||||||
class StatusMessageModel(BaseModel):
|
class StatusMessageModel(BaseModel):
|
||||||
status:str
|
status:str
|
||||||
|
|
||||||
@app.get('/api/service/{service_port}/stop', response_model=StatusMessageModel)
|
@app.get('/api/service/{service_id}/stop', response_model=StatusMessageModel)
|
||||||
async def service_stop(service_port: int, auth: bool = Depends(is_loggined)):
|
async def service_stop(service_id: str, auth: bool = Depends(is_loggined)):
|
||||||
"""Request the stop of a specific service"""
|
"""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()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/api/service/{service_port}/start', response_model=StatusMessageModel)
|
@app.get('/api/service/{service_id}/start', response_model=StatusMessageModel)
|
||||||
async def service_start(service_port: int, auth: bool = Depends(is_loggined)):
|
async def service_start(service_id: str, auth: bool = Depends(is_loggined)):
|
||||||
"""Request the start of a specific service"""
|
"""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()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/api/service/{service_port}/delete', response_model=StatusMessageModel)
|
@app.get('/api/service/{service_id}/delete', response_model=StatusMessageModel)
|
||||||
async def service_delete(service_port: int, auth: bool = Depends(is_loggined)):
|
async def service_delete(service_id: str, auth: bool = Depends(is_loggined)):
|
||||||
"""Request the deletion of a specific service"""
|
"""Request the deletion of a specific service"""
|
||||||
db.query('DELETE FROM services WHERE port = ?;', service_port)
|
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
||||||
db.query('DELETE FROM regexes WHERE service_port = ?;', service_port)
|
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
|
||||||
await firewall.remove(service_port)
|
await firewall.remove(service_id)
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
class RenameForm(BaseModel):
|
class RenameForm(BaseModel):
|
||||||
name:str
|
name:str
|
||||||
|
|
||||||
@app.post('/api/service/{service_port}/rename', response_model=StatusMessageModel)
|
@app.post('/api/service/{service_id}/rename', response_model=StatusMessageModel)
|
||||||
async def service_rename(service_port: int, form: RenameForm, auth: bool = Depends(is_loggined)):
|
async def service_rename(service_id: str, form: RenameForm, auth: bool = Depends(is_loggined)):
|
||||||
"""Request to change the name of a specific service"""
|
"""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!'}
|
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()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@@ -234,28 +245,28 @@ class RegexModel(BaseModel):
|
|||||||
regex:str
|
regex:str
|
||||||
mode:str
|
mode:str
|
||||||
id:int
|
id:int
|
||||||
service_port:int
|
service_id:str
|
||||||
is_blacklist: bool
|
is_blacklist: bool
|
||||||
n_packets:int
|
n_packets:int
|
||||||
is_case_sensitive:bool
|
is_case_sensitive:bool
|
||||||
active:bool
|
active:bool
|
||||||
|
|
||||||
@app.get('/api/service/{service_port}/regexes', response_model=List[RegexModel])
|
@app.get('/api/service/{service_id}/regexes', response_model=List[RegexModel])
|
||||||
async def get_service_regexe_list(service_port: int, auth: bool = Depends(is_loggined)):
|
async def get_service_regexe_list(service_id: str, auth: bool = Depends(is_loggined)):
|
||||||
"""Get the list of the regexes of a service"""
|
"""Get the list of the regexes of a service"""
|
||||||
return db.query("""
|
return db.query("""
|
||||||
SELECT
|
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
|
blocked_packets n_packets, is_case_sensitive, active
|
||||||
FROM regexes WHERE service_port = ?;
|
FROM regexes WHERE service_id = ?;
|
||||||
""", service_port)
|
""", service_id)
|
||||||
|
|
||||||
@app.get('/api/regex/{regex_id}', response_model=RegexModel)
|
@app.get('/api/regex/{regex_id}', response_model=RegexModel)
|
||||||
async def get_regex_by_id(regex_id: int, auth: bool = Depends(is_loggined)):
|
async def get_regex_by_id(regex_id: int, auth: bool = Depends(is_loggined)):
|
||||||
"""Get regex info using his id"""
|
"""Get regex info using his id"""
|
||||||
res = db.query("""
|
res = db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
regex, mode, regex_id `id`, service_port, is_blacklist,
|
regex, mode, regex_id `id`, service_id, is_blacklist,
|
||||||
blocked_packets n_packets, is_case_sensitive, active
|
blocked_packets n_packets, is_case_sensitive, active
|
||||||
FROM regexes WHERE `id` = ?;
|
FROM regexes WHERE `id` = ?;
|
||||||
""", regex_id)
|
""", regex_id)
|
||||||
@@ -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)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
db.query('DELETE FROM regexes WHERE regex_id = ?;', regex_id)
|
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()
|
await refresh_frontend()
|
||||||
|
|
||||||
return {'status': 'ok'}
|
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)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
db.query('UPDATE regexes SET active=1 WHERE regex_id = ?;', regex_id)
|
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()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
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)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
db.query('UPDATE regexes SET active=0 WHERE regex_id = ?;', regex_id)
|
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()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
class RegexAddForm(BaseModel):
|
class RegexAddForm(BaseModel):
|
||||||
service_port: int
|
service_id: str
|
||||||
regex: str
|
regex: str
|
||||||
mode: str
|
mode: str
|
||||||
active: Union[bool,None]
|
active: Union[bool,None]
|
||||||
@@ -309,31 +320,39 @@ async def add_new_regex(form: RegexAddForm, auth: bool = Depends(is_loggined)):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return {"status":"Invalid regex"}
|
return {"status":"Invalid regex"}
|
||||||
try:
|
try:
|
||||||
db.query("INSERT INTO regexes (service_port, regex, is_blacklist, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?, ?);",
|
db.query("INSERT INTO regexes (service_id, 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 )
|
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:
|
except sqlite3.IntegrityError:
|
||||||
return {'status': 'An identical regex already exists'}
|
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()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
class ServiceAddForm(BaseModel):
|
class ServiceAddForm(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
port: int
|
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)):
|
async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined)):
|
||||||
"""Add a new service"""
|
"""Add a new service"""
|
||||||
|
import time
|
||||||
|
srv_id = None
|
||||||
try:
|
try:
|
||||||
db.query("INSERT INTO services (name, port, status) VALUES (?, ?, ?)",
|
srv_id = str(form.port)+"::"+("ipv6" if form.ipv6 else "ipv4")
|
||||||
form.name, form.port, STATUS.STOP)
|
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:
|
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()
|
await firewall.reload()
|
||||||
|
init_t = time.time()
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
|
return {'status': 'ok', 'service_id': srv_id}
|
||||||
return {'status': 'ok'}
|
|
||||||
|
|
||||||
async def frontend_debug_proxy(path):
|
async def frontend_debug_proxy(path):
|
||||||
httpc = httpx.AsyncClient()
|
httpc = httpx.AsyncClient()
|
||||||
|
|||||||
105
backend/proxy.py
105
backend/proxy.py
@@ -1,24 +1,25 @@
|
|||||||
import multiprocessing
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import List
|
from typing import List
|
||||||
from netfilterqueue import NetfilterQueue
|
from netfilterqueue import NetfilterQueue
|
||||||
from multiprocessing import Manager, Process
|
from multiprocessing import Manager, Process
|
||||||
from scapy.all import IP, TCP, UDP
|
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
import os, traceback, pcre, re
|
import os, traceback, pcre, re
|
||||||
|
|
||||||
QUEUE_BASE_NUM = 1000
|
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")
|
if len_list <= 0: raise Exception("len must be >= 1")
|
||||||
queue_list = []
|
queue_list = []
|
||||||
starts = QUEUE_BASE_NUM
|
starts = QUEUE_BASE_NUM
|
||||||
end = starts
|
end = starts
|
||||||
|
|
||||||
def func_wrap(pkt):
|
def func_wrap(pkt):
|
||||||
pkt_parsed = IP(pkt.get_payload())
|
pkt_parsed = IPv6(pkt.get_payload()) if ipv6 else IP(pkt.get_payload())
|
||||||
try:
|
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()
|
else: pkt.accept()
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -52,15 +53,16 @@ class ProtoTypes:
|
|||||||
|
|
||||||
class IPTables:
|
class IPTables:
|
||||||
|
|
||||||
@staticmethod
|
def __init__(self, ipv6=False):
|
||||||
def command(params):
|
self.ipv6 = ipv6
|
||||||
|
|
||||||
|
def command(self, params):
|
||||||
if os.geteuid() != 0:
|
if os.geteuid() != 0:
|
||||||
exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
|
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(self, param):
|
||||||
def list_filters(param):
|
stdout, strerr = self.command(["-L", str(param), "--line-number", "-n"])
|
||||||
stdout, strerr = IPTables.command(["-L", str(param), "--line-number", "-n"])
|
|
||||||
output = [ele.split() for ele in stdout.decode().split("\n")]
|
output = [ele.split() for ele in stdout.decode().split("\n")]
|
||||||
return [{
|
return [{
|
||||||
"id": ele[0],
|
"id": ele[0],
|
||||||
@@ -72,42 +74,35 @@ class IPTables:
|
|||||||
"details": " ".join(ele[6:]) if len(ele) >= 7 else "",
|
"details": " ".join(ele[6:]) if len(ele) >= 7 else "",
|
||||||
} for ele in output if len(ele) >= 6 and ele[0].isnumeric()]
|
} for ele in output if len(ele) >= 6 and ele[0].isnumeric()]
|
||||||
|
|
||||||
@staticmethod
|
def delete_command(self, param, id):
|
||||||
def delete_command(param, id):
|
self.command(["-R", str(param), str(id)])
|
||||||
IPTables.command(["-R", str(param), str(id)])
|
|
||||||
|
|
||||||
@staticmethod
|
def create_chain(self, name):
|
||||||
def create_chain(name):
|
self.command(["-N", str(name)])
|
||||||
IPTables.command(["-N", str(name)])
|
|
||||||
|
|
||||||
@staticmethod
|
def flush_chain(self, name):
|
||||||
def flush_chain(name):
|
self.command(["-F", str(name)])
|
||||||
IPTables.command(["-F", str(name)])
|
|
||||||
|
|
||||||
@staticmethod
|
def add_chain_to_input(self, name):
|
||||||
def add_chain_to_input(name):
|
self.command(["-I", "INPUT", "-j", str(name)])
|
||||||
IPTables.command(["-I", "INPUT", "-j", str(name)])
|
|
||||||
|
|
||||||
@staticmethod
|
def add_chain_to_output(self, name):
|
||||||
def add_chain_to_output(name):
|
self.command(["-I", "OUTPUT", "-j", str(name)])
|
||||||
IPTables.command(["-I", "OUTPUT", "-j", str(name)])
|
|
||||||
|
|
||||||
@staticmethod
|
def add_s_to_c(self, proto, port, queue_range):
|
||||||
def add_s_to_c(proto, port, queue_range):
|
|
||||||
init, end = queue_range
|
init, end = queue_range
|
||||||
if init > end: init, end = end, init
|
if init > end: init, end = end, init
|
||||||
IPTables.command([
|
self.command([
|
||||||
"-A", FilterTypes.OUTPUT, "-p", str(proto),
|
"-A", FilterTypes.OUTPUT, "-p", str(proto),
|
||||||
"--sport", str(port), "-j", "NFQUEUE",
|
"--sport", str(port), "-j", "NFQUEUE",
|
||||||
"--queue-num" if init == end else "--queue-balance",
|
"--queue-num" if init == end else "--queue-balance",
|
||||||
f"{init}" if init == end else f"{init}:{end}", "--queue-bypass"
|
f"{init}" if init == end else f"{init}:{end}", "--queue-bypass"
|
||||||
])
|
])
|
||||||
|
|
||||||
@staticmethod
|
def add_c_to_s(self, proto, port, queue_range):
|
||||||
def add_c_to_s(proto, port, queue_range):
|
|
||||||
init, end = queue_range
|
init, end = queue_range
|
||||||
if init > end: init, end = end, init
|
if init > end: init, end = end, init
|
||||||
IPTables.command([
|
self.command([
|
||||||
"-A", FilterTypes.INPUT, "-p", str(proto),
|
"-A", FilterTypes.INPUT, "-p", str(proto),
|
||||||
"--dport", str(port), "-j", "NFQUEUE",
|
"--dport", str(port), "-j", "NFQUEUE",
|
||||||
"--queue-num" if init == end else "--queue-balance",
|
"--queue-num" if init == end else "--queue-balance",
|
||||||
@@ -115,41 +110,45 @@ class IPTables:
|
|||||||
])
|
])
|
||||||
|
|
||||||
class FiregexFilter():
|
class FiregexFilter():
|
||||||
def __init__(self, type, number, queue, proto, port):
|
def __init__(self, type, number, queue, proto, port, ipv6):
|
||||||
self.type = type
|
self.type = type
|
||||||
self.id = int(number)
|
self.id = int(number)
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
self.port = int(port)
|
self.port = int(port)
|
||||||
|
self.iptable = IPTables(ipv6)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<FiregexFilter type={self.type} id={self.id} port={self.port} proto={self.proto} queue={self.queue}>"
|
return f"<FiregexFilter type={self.type} id={self.id} port={self.port} proto={self.proto} queue={self.queue}>"
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
IPTables.delete_command(self.type, self.id)
|
self.iptable.delete_command(self.type, self.id)
|
||||||
|
|
||||||
class FiregexFilterManager:
|
class FiregexFilterManager:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ipv6):
|
||||||
IPTables.create_chain(FilterTypes.INPUT)
|
self.ipv6 = ipv6
|
||||||
IPTables.create_chain(FilterTypes.OUTPUT)
|
self.iptables = IPTables(ipv6)
|
||||||
|
self.iptables.create_chain(FilterTypes.INPUT)
|
||||||
|
self.iptables.create_chain(FilterTypes.OUTPUT)
|
||||||
input_found = False
|
input_found = False
|
||||||
output_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:
|
if filter["target"] == FilterTypes.INPUT:
|
||||||
input_found = True
|
input_found = True
|
||||||
break
|
break
|
||||||
for filter in IPTables.list_filters("OUTPUT"):
|
for filter in self.iptables.list_filters("OUTPUT"):
|
||||||
if filter["target"] == FilterTypes.OUTPUT:
|
if filter["target"] == FilterTypes.OUTPUT:
|
||||||
output_found = True
|
output_found = True
|
||||||
break
|
break
|
||||||
if not input_found: IPTables.add_chain_to_input(FilterTypes.INPUT)
|
if not input_found: self.iptables.add_chain_to_input(FilterTypes.INPUT)
|
||||||
if not output_found: IPTables.add_chain_to_output(FilterTypes.OUTPUT)
|
if not output_found: self.iptables.add_chain_to_output(FilterTypes.OUTPUT)
|
||||||
|
|
||||||
|
|
||||||
def get(self) -> List[FiregexFilter]:
|
def get(self) -> List[FiregexFilter]:
|
||||||
res = []
|
res = []
|
||||||
for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]:
|
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
|
queue_num = None
|
||||||
balanced = re.findall(r"NFQUEUE balance ([0-9]+):([0-9]+)", filter["details"])
|
balanced = re.findall(r"NFQUEUE balance ([0-9]+):([0-9]+)", filter["details"])
|
||||||
numbered = re.findall(r"NFQUEUE num ([0-9]+)", filter["details"])
|
numbered = re.findall(r"NFQUEUE num ([0-9]+)", filter["details"])
|
||||||
@@ -162,7 +161,8 @@ class FiregexFilterManager:
|
|||||||
number=filter["id"],
|
number=filter["id"],
|
||||||
queue=queue_num,
|
queue=queue_num,
|
||||||
proto=filter["prot"],
|
proto=filter["prot"],
|
||||||
port=int(port[0])
|
port=int(port[0]),
|
||||||
|
ipv6=self.ipv6
|
||||||
))
|
))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -170,18 +170,18 @@ class FiregexFilterManager:
|
|||||||
for ele in self.get():
|
for ele in self.get():
|
||||||
if int(port) == ele.port: return None
|
if int(port) == ele.port: return None
|
||||||
|
|
||||||
def c_to_s(pkt, data): return func(pkt, data, True)
|
def c_to_s(pkt, data, payload): return func(pkt, data, payload, True)
|
||||||
def s_to_c(pkt, data): return func(pkt, data, False)
|
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)
|
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)
|
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
|
return queues_c_to_s + queues_s_to_c
|
||||||
|
|
||||||
def delete_all(self):
|
def delete_all(self):
|
||||||
for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]:
|
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):
|
def delete_by_port(self, port):
|
||||||
for filter in self.get():
|
for filter in self.get():
|
||||||
@@ -209,8 +209,8 @@ class Filter:
|
|||||||
return True if self.compiled_regex.search(data) else False
|
return True if self.compiled_regex.search(data) else False
|
||||||
|
|
||||||
class Proxy:
|
class Proxy:
|
||||||
def __init__(self, port, filters=None):
|
def __init__(self, port, ipv6, filters=None):
|
||||||
self.manager = FiregexFilterManager()
|
self.manager = FiregexFilterManager(ipv6)
|
||||||
self.port = port
|
self.port = port
|
||||||
self.filters = Manager().list(filters) if filters else Manager().list([])
|
self.filters = Manager().list(filters) if filters else Manager().list([])
|
||||||
self.process = None
|
self.process = None
|
||||||
@@ -224,8 +224,7 @@ class Proxy:
|
|||||||
|
|
||||||
def _starter(self):
|
def _starter(self):
|
||||||
self.manager.delete_by_port(self.port)
|
self.manager.delete_by_port(self.port)
|
||||||
def regex_filter(pkt, data, by_client):
|
def regex_filter(pkt, data, packet, by_client):
|
||||||
packet = bytes(data[TCP if TCP in data else UDP].payload)
|
|
||||||
try:
|
try:
|
||||||
for i, filter in enumerate(self.filters):
|
for i, filter in enumerate(self.filters):
|
||||||
if (by_client and filter.c_to_s) or (not by_client and filter.s_to_c):
|
if (by_client and filter.c_to_s) or (not by_client and filter.s_to_c):
|
||||||
|
|||||||
@@ -49,27 +49,30 @@ class SQLite():
|
|||||||
self.connect()
|
self.connect()
|
||||||
self.create_schema({
|
self.create_schema({
|
||||||
'services': {
|
'services': {
|
||||||
|
'service_id': 'VARCHAR(100) PRIMARY KEY',
|
||||||
'status': 'VARCHAR(100) NOT NULL',
|
'status': 'VARCHAR(100) NOT NULL',
|
||||||
'port': 'INT NOT NULL CHECK(port > 0 and port < 65536) UNIQUE PRIMARY KEY',
|
'port': 'INT NOT NULL CHECK(port > 0 and port < 65536)',
|
||||||
'name': 'VARCHAR(100) NOT NULL'
|
'name': 'VARCHAR(100) NOT NULL UNIQUE',
|
||||||
|
'ipv6': 'BOOLEAN NOT NULL CHECK (ipv6 IN (0, 1)) DEFAULT 0',
|
||||||
},
|
},
|
||||||
'regexes': {
|
'regexes': {
|
||||||
'regex': 'TEXT NOT NULL',
|
'regex': 'TEXT NOT NULL',
|
||||||
'mode': 'VARCHAR(1) 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))',
|
'is_blacklist': 'BOOLEAN NOT NULL CHECK (is_blacklist IN (0, 1))',
|
||||||
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
|
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
|
||||||
'regex_id': 'INTEGER PRIMARY KEY',
|
'regex_id': 'INTEGER PRIMARY KEY',
|
||||||
'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))',
|
'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))',
|
||||||
'active' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1)) DEFAULT 1',
|
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1)) DEFAULT 1',
|
||||||
'FOREIGN KEY (service_port)':'REFERENCES services (port)',
|
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)',
|
||||||
},
|
},
|
||||||
'keys_values': {
|
'keys_values': {
|
||||||
'key': 'VARCHAR(100) PRIMARY KEY',
|
'key': 'VARCHAR(100) PRIMARY KEY',
|
||||||
'value': 'VARCHAR(100) NOT NULL',
|
'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:
|
class KeyValueStorage:
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
@@ -95,10 +98,11 @@ class STATUS:
|
|||||||
class ServiceNotFoundException(Exception): pass
|
class ServiceNotFoundException(Exception): pass
|
||||||
|
|
||||||
class ServiceManager:
|
class ServiceManager:
|
||||||
def __init__(self, port, db):
|
def __init__(self, id, port, ipv6, db):
|
||||||
|
self.id = id
|
||||||
self.port = port
|
self.port = port
|
||||||
self.db = db
|
self.db = db
|
||||||
self.proxy = Proxy(port)
|
self.proxy = Proxy(port, ipv6)
|
||||||
self.status = STATUS.STOP
|
self.status = STATUS.STOP
|
||||||
self.filters = {}
|
self.filters = {}
|
||||||
self._update_filters_from_db()
|
self._update_filters_from_db()
|
||||||
@@ -110,8 +114,8 @@ class ServiceManager:
|
|||||||
SELECT
|
SELECT
|
||||||
regex, mode, regex_id `id`, is_blacklist,
|
regex, mode, regex_id `id`, is_blacklist,
|
||||||
blocked_packets n_packets, is_case_sensitive
|
blocked_packets n_packets, is_case_sensitive
|
||||||
FROM regexes WHERE service_port = ? AND active=1;
|
FROM regexes WHERE service_id = ? AND active=1;
|
||||||
""", self.port)
|
""", self.id)
|
||||||
|
|
||||||
#Filter check
|
#Filter check
|
||||||
old_filters = set(self.filters.keys())
|
old_filters = set(self.filters.keys())
|
||||||
@@ -137,7 +141,7 @@ class ServiceManager:
|
|||||||
self.proxy.set_filters(self.filters.values())
|
self.proxy.set_filters(self.filters.values())
|
||||||
|
|
||||||
def __update_status_db(self, status):
|
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 def next(self,to):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
@@ -190,11 +194,11 @@ class ProxyManager:
|
|||||||
for key in list(self.proxy_table.keys()):
|
for key in list(self.proxy_table.keys()):
|
||||||
await self.remove(key)
|
await self.remove(key)
|
||||||
|
|
||||||
async def remove(self,port):
|
async def remove(self,srv_id):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
if port in self.proxy_table:
|
if srv_id in self.proxy_table:
|
||||||
await self.proxy_table[port].next(STATUS.STOP)
|
await self.proxy_table[srv_id].next(STATUS.STOP)
|
||||||
del self.proxy_table[port]
|
del self.proxy_table[srv_id]
|
||||||
|
|
||||||
async def init(self, callback = None):
|
async def init(self, callback = None):
|
||||||
self.init_updater(callback)
|
self.init_updater(callback)
|
||||||
@@ -202,13 +206,13 @@ class ProxyManager:
|
|||||||
|
|
||||||
async def reload(self):
|
async def reload(self):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
for srv in self.db.query('SELECT port, status FROM services;'):
|
for srv in self.db.query('SELECT service_id, port, status, ipv6 FROM services;'):
|
||||||
srv_port, req_status = srv["port"], srv["status"]
|
srv_id, srv_port, req_status, srv_ipv6 = srv["service_id"], srv["port"], srv["status"], srv["ipv6"]
|
||||||
if srv_port in self.proxy_table:
|
if srv_port in self.proxy_table:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.proxy_table[srv_port] = ServiceManager(srv_port,self.db)
|
self.proxy_table[srv_id] = ServiceManager(srv_id, srv_port, srv_ipv6, self.db)
|
||||||
await self.proxy_table[srv_port].next(req_status)
|
await self.proxy_table[srv_id].next(req_status)
|
||||||
|
|
||||||
async def _stats_updater(self, callback):
|
async def _stats_updater(self, callback):
|
||||||
try:
|
try:
|
||||||
@@ -226,10 +230,13 @@ class ProxyManager:
|
|||||||
self.updater_task = None
|
self.updater_task = None
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def get(self,srv_id):
|
||||||
|
if srv_id in self.proxy_table:
|
||||||
def get(self,port):
|
return self.proxy_table[srv_id]
|
||||||
if port in self.proxy_table:
|
|
||||||
return self.proxy_table[port]
|
|
||||||
else:
|
else:
|
||||||
raise ServiceNotFoundException()
|
raise ServiceNotFoundException()
|
||||||
|
|
||||||
|
def refactor_name(name:str):
|
||||||
|
name = name.strip()
|
||||||
|
while " " in name: name = name.replace(" "," ")
|
||||||
|
return name
|
||||||
@@ -15,7 +15,7 @@ type RegexAddInfo = {
|
|||||||
deactive:boolean
|
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({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -48,7 +48,7 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
|
|||||||
const request:RegexAddForm = {
|
const request:RegexAddForm = {
|
||||||
is_blacklist:values.type !== "whitelist",
|
is_blacklist:values.type !== "whitelist",
|
||||||
is_case_sensitive: !values.is_case_insensitive,
|
is_case_sensitive: !values.is_case_insensitive,
|
||||||
service_port: service,
|
service_id: service,
|
||||||
mode: filter_mode?filter_mode:"B",
|
mode: filter_mode?filter_mode:"B",
|
||||||
regex: b64encode(values.regex),
|
regex: b64encode(values.regex),
|
||||||
active: !values.deactive
|
active: !values.deactive
|
||||||
@@ -58,7 +58,7 @@ function AddNewRegex({ opened, onClose, service }:{ opened:boolean, onClose:()=>
|
|||||||
if (!res){
|
if (!res){
|
||||||
setSubmitLoading(false)
|
setSubmitLoading(false)
|
||||||
close();
|
close();
|
||||||
okNotify(`Regex ${b64decode(request.regex)} has been added`, `Successfully added ${request.is_case_sensitive?"case sensitive":"case insensitive"} ${request.is_blacklist?"blacklist":"whitelist"} regex to ${request.service_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"){
|
}else if (res.toLowerCase() === "invalid regex"){
|
||||||
setSubmitLoading(false)
|
setSubmitLoading(false)
|
||||||
form.setFieldError("regex", "Invalid Regex")
|
form.setFieldError("regex", "Invalid Regex")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ImCross } from "react-icons/im"
|
|||||||
type ServiceAddForm = {
|
type ServiceAddForm = {
|
||||||
name:string,
|
name:string,
|
||||||
port:number,
|
port:number,
|
||||||
|
ipv6:boolean,
|
||||||
autostart: boolean,
|
autostart: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
|||||||
initialValues: {
|
initialValues: {
|
||||||
name:"",
|
name:"",
|
||||||
port:8080,
|
port:8080,
|
||||||
|
ipv6:false,
|
||||||
autostart: true
|
autostart: true
|
||||||
},
|
},
|
||||||
validationRules:{
|
validationRules:{
|
||||||
@@ -33,13 +35,13 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
|||||||
const [submitLoading, setSubmitLoading] = useState(false)
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
const [error, setError] = useState<string|null>(null)
|
const [error, setError] = useState<string|null>(null)
|
||||||
|
|
||||||
const submitRequest = ({ name, port, autostart }:ServiceAddForm) =>{
|
const submitRequest = ({ name, port, autostart, ipv6 }:ServiceAddForm) =>{
|
||||||
setSubmitLoading(true)
|
setSubmitLoading(true)
|
||||||
addservice({name, port}).then( res => {
|
addservice({name, port, ipv6}).then( res => {
|
||||||
if (res.status === "ok"){
|
if (res.status === "ok" && res.service_id){
|
||||||
setSubmitLoading(false)
|
setSubmitLoading(false)
|
||||||
close();
|
close();
|
||||||
if (autostart) startservice(port)
|
if (autostart) startservice(res.service_id)
|
||||||
okNotify(`Service ${name} has been added`, `Successfully added service with port ${port}`)
|
okNotify(`Service ${name} has been added`, `Successfully added service with port ${port}`)
|
||||||
}else{
|
}else{
|
||||||
setSubmitLoading(false)
|
setSubmitLoading(false)
|
||||||
@@ -76,6 +78,13 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
|||||||
{...form.getInputProps('autostart', { type: 'checkbox' })}
|
{...form.getInputProps('autostart', { type: 'checkbox' })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Space h="sm" />
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
label="Filter on Ipv6"
|
||||||
|
{...form.getInputProps('ipv6', { type: 'checkbox' })}
|
||||||
|
/>
|
||||||
|
|
||||||
<Group position="right" mt="md">
|
<Group position="right" mt="md">
|
||||||
<Button loading={submitLoading} type="submit">Add Service</Button>
|
<Button loading={submitLoading} type="submit">Add Service</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ function Header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {srv} = useParams()
|
const {srv} = useParams()
|
||||||
const service_port = srv?parseInt(srv):null
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const closeModal = () => {setOpen(false);}
|
const closeModal = () => {setOpen(false);}
|
||||||
@@ -125,7 +124,7 @@ function Header() {
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
:null}
|
: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">
|
<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"
|
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
|
||||||
aria-describedby="tooltip-add-id"
|
aria-describedby="tooltip-add-id"
|
||||||
@@ -140,8 +139,8 @@ function Header() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
|
|
||||||
{service_port?
|
{srv?
|
||||||
<AddNewRegex opened={open} onClose={closeModal} service={service_port} />:
|
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
|
||||||
<AddNewService opened={open} onClose={closeModal} />
|
<AddNewService opened={open} onClose={closeModal} />
|
||||||
}
|
}
|
||||||
<Modal size="xl" title="Change Firewall Password" opened={changePasswordModal} onClose={()=>setChangePasswordModal(false)} closeOnClickOutside={false} centered>
|
<Modal size="xl" title="Change Firewall Password" opened={changePasswordModal} onClose={()=>setChangePasswordModal(false)} closeOnClickOutside={false} centered>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ function RegexView({ regexInfo }:{ regexInfo:RegexFilter }) {
|
|||||||
/>
|
/>
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
<div className='center-flex'>
|
<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" />
|
<Space w="xs" />
|
||||||
<Badge size="md" color={regexInfo.active?"lime":"red"} variant="filled">{regexInfo.active?"ACTIVE":"DISABLED"}</Badge>
|
<Badge size="md" color={regexInfo.active?"lime":"red"} variant="filled">{regexInfo.active?"ACTIVE":"DISABLED"}</Badge>
|
||||||
<Space w="xs" />
|
<Space w="xs" />
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>v
|
|||||||
|
|
||||||
const submitRequest = ({ name }:{ name:string }) => {
|
const submitRequest = ({ name }:{ name:string }) => {
|
||||||
setSubmitLoading(true)
|
setSubmitLoading(true)
|
||||||
renameservice(service.port, name).then( res => {
|
renameservice(service.service_id, name).then( res => {
|
||||||
if (!res){
|
if (!res){
|
||||||
setSubmitLoading(false)
|
setSubmitLoading(false)
|
||||||
close();
|
close();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
|
|||||||
|
|
||||||
const stopService = async () => {
|
const stopService = async () => {
|
||||||
setButtonLoading(true)
|
setButtonLoading(true)
|
||||||
await stopservice(service.port).then(res => {
|
await stopservice(service.service_id).then(res => {
|
||||||
if(!res){
|
if(!res){
|
||||||
okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.port} has been stopped!`)
|
okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.port} has been stopped!`)
|
||||||
}else{
|
}else{
|
||||||
@@ -39,7 +39,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
|
|||||||
|
|
||||||
const startService = async () => {
|
const startService = async () => {
|
||||||
setButtonLoading(true)
|
setButtonLoading(true)
|
||||||
await startservice(service.port).then(res => {
|
await startservice(service.service_id).then(res => {
|
||||||
if(!res){
|
if(!res){
|
||||||
okNotify(`Service ${service.name} started successfully!`,`The service on ${service.port} has been started!`)
|
okNotify(`Service ${service.name} started successfully!`,`The service on ${service.port} has been started!`)
|
||||||
}else{
|
}else{
|
||||||
@@ -52,7 +52,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteService = () => {
|
const deleteService = () => {
|
||||||
deleteservice(service.port).then(res => {
|
deleteservice(service.service_id).then(res => {
|
||||||
if (!res){
|
if (!res){
|
||||||
okNotify("Service delete complete!",`The service ${service.name} has been deleted!`)
|
okNotify("Service delete complete!",`The service ${service.name} has been deleted!`)
|
||||||
}else
|
}else
|
||||||
@@ -94,8 +94,11 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
|
|||||||
</MediaQuery>
|
</MediaQuery>
|
||||||
|
|
||||||
<div className="center-flex-row">
|
<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>
|
<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>
|
</div>
|
||||||
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
|
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
|
||||||
<div className='flex-spacer' />
|
<div className='flex-spacer' />
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ export type GeneralStats = {
|
|||||||
|
|
||||||
export type Service = {
|
export type Service = {
|
||||||
name:string,
|
name:string,
|
||||||
|
service_id:string,
|
||||||
status:string,
|
status:string,
|
||||||
port:number,
|
port:number,
|
||||||
|
ipv6:boolean,
|
||||||
n_packets:number,
|
n_packets:number,
|
||||||
n_regex:number,
|
n_regex:number,
|
||||||
}
|
}
|
||||||
@@ -15,9 +17,15 @@ export type Service = {
|
|||||||
export type ServiceAddForm = {
|
export type ServiceAddForm = {
|
||||||
name:string,
|
name:string,
|
||||||
port:number,
|
port:number,
|
||||||
|
ipv6:boolean,
|
||||||
internalPort?:number
|
internalPort?:number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ServiceAddResponse = {
|
||||||
|
status: string,
|
||||||
|
service_id?: string,
|
||||||
|
}
|
||||||
|
|
||||||
export type ServerResponse = {
|
export type ServerResponse = {
|
||||||
status:string
|
status:string
|
||||||
}
|
}
|
||||||
@@ -49,7 +57,7 @@ export type ChangePassword = {
|
|||||||
|
|
||||||
export type RegexFilter = {
|
export type RegexFilter = {
|
||||||
id:number,
|
id:number,
|
||||||
service_port:number,
|
service_id:string,
|
||||||
regex:string
|
regex:string
|
||||||
is_blacklist:boolean,
|
is_blacklist:boolean,
|
||||||
is_case_sensitive:boolean,
|
is_case_sensitive:boolean,
|
||||||
@@ -59,7 +67,7 @@ export type RegexFilter = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type RegexAddForm = {
|
export type RegexAddForm = {
|
||||||
service_port:number,
|
service_id:string,
|
||||||
regex:string,
|
regex:string,
|
||||||
is_case_sensitive:boolean,
|
is_case_sensitive:boolean,
|
||||||
is_blacklist:boolean,
|
is_blacklist:boolean,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { showNotification } from "@mantine/notifications";
|
import { showNotification } from "@mantine/notifications";
|
||||||
import { ImCross } from "react-icons/im";
|
import { ImCross } from "react-icons/im";
|
||||||
import { TiTick } from "react-icons/ti"
|
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
|
var Buffer = require('buffer').Buffer
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ export async function servicelist(){
|
|||||||
return await getapi("services") as Service[];
|
return await getapi("services") as Service[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function serviceinfo(service_port:number){
|
export async function serviceinfo(service_id:string){
|
||||||
return await getapi(`service/${service_port}`) as Service;
|
return await getapi(`service/${service_id}`) as Service;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout(){
|
export async function logout(){
|
||||||
@@ -109,27 +109,27 @@ export async function deactivateregex(regex_id:number){
|
|||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startservice(service_port:number){
|
export async function startservice(service_id:string){
|
||||||
const { status } = await getapi(`service/${service_port}/start`) as ServerResponse;
|
const { status } = await getapi(`service/${service_id}/start`) as ServerResponse;
|
||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renameservice(service_port:number, name: string){
|
export async function renameservice(service_id:string, name: string){
|
||||||
const { status } = await postapi(`service/${service_port}/rename`,{ name }) as ServerResponse;
|
const { status } = await postapi(`service/${service_id}/rename`,{ name }) as ServerResponse;
|
||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopservice(service_port:number){
|
export async function stopservice(service_id:string){
|
||||||
const { status } = await getapi(`service/${service_port}/stop`) as ServerResponse;
|
const { status } = await getapi(`service/${service_id}/stop`) as ServerResponse;
|
||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addservice(data:ServiceAddForm) {
|
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) {
|
export async function deleteservice(service_id:string) {
|
||||||
const { status } = await getapi(`service/${service_port}/delete`) as ServerResponse;
|
const { status } = await getapi(`service/${service_id}/delete`) as ServerResponse;
|
||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +139,8 @@ export async function addregex(data:RegexAddForm) {
|
|||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function serviceregexlist(service_port:number){
|
export async function serviceregexlist(service_id:string){
|
||||||
return await getapi(`service/${service_port}/regexes`) as RegexFilter[];
|
return await getapi(`service/${service_id}/regexes`) as RegexFilter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ function HomePage() {
|
|||||||
|
|
||||||
return <div id="service-list" className="center-flex-row">
|
return <div id="service-list" className="center-flex-row">
|
||||||
<LoadingOverlay visible={loader} />
|
<LoadingOverlay visible={loader} />
|
||||||
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.port} onClick={()=>{
|
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.service_id} onClick={()=>{
|
||||||
navigator("/"+srv.port)
|
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"/> <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" />
|
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
|
||||||
<div className='center-flex'>
|
<div className='center-flex'>
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ import { useWindowEvent } from '@mantine/hooks';
|
|||||||
|
|
||||||
function ServiceDetails() {
|
function ServiceDetails() {
|
||||||
const {srv} = useParams()
|
const {srv} = useParams()
|
||||||
const service_port = srv?parseInt(srv):null
|
|
||||||
const [serviceInfo, setServiceInfo] = useState<Service>({
|
const [serviceInfo, setServiceInfo] = useState<Service>({
|
||||||
|
service_id: "",
|
||||||
port:0,
|
port:0,
|
||||||
n_packets:0,
|
n_packets:0,
|
||||||
n_regex:0,
|
n_regex:0,
|
||||||
name:"",
|
name:"",
|
||||||
|
ipv6:false,
|
||||||
status:"🤔"
|
status:"🤔"
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -26,9 +27,9 @@ function ServiceDetails() {
|
|||||||
const closeModal = () => {setOpen(false);updateInfo();}
|
const closeModal = () => {setOpen(false);updateInfo();}
|
||||||
|
|
||||||
const updateInfo = async () => {
|
const updateInfo = async () => {
|
||||||
if (!service_port) return
|
if (!srv) return
|
||||||
let error = false;
|
let error = false;
|
||||||
await serviceinfo(service_port).then(res => {
|
await serviceinfo(srv).then(res => {
|
||||||
setServiceInfo(res)
|
setServiceInfo(res)
|
||||||
}).catch(
|
}).catch(
|
||||||
err =>{
|
err =>{
|
||||||
@@ -36,10 +37,10 @@ function ServiceDetails() {
|
|||||||
navigator("/")
|
navigator("/")
|
||||||
})
|
})
|
||||||
if (error) return
|
if (error) return
|
||||||
await serviceregexlist(service_port).then(res => {
|
await serviceregexlist(srv).then(res => {
|
||||||
setRegexesList(res)
|
setRegexesList(res)
|
||||||
}).catch(
|
}).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)
|
setLoader(false)
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ function ServiceDetails() {
|
|||||||
</Grid>
|
</Grid>
|
||||||
}
|
}
|
||||||
|
|
||||||
{service_port?<AddNewRegex opened={open} onClose={closeModal} service={service_port} />:null}
|
{srv?<AddNewRegex opened={open} onClose={closeModal} service={srv} />:null}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user