IP interface and Protocol + fix iptables

This commit is contained in:
DomySh
2022-07-12 15:47:31 +02:00
parent c4ab4c628e
commit 3034c66423
13 changed files with 243 additions and 145 deletions

View File

@@ -10,6 +10,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt from jose import JWTError, jwt
from passlib.context import CryptContext from passlib.context import CryptContext
from fastapi_socketio import SocketManager from fastapi_socketio import SocketManager
from ipaddress import ip_interface
ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER" ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER"
DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG" DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG"
@@ -24,7 +25,7 @@ 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.4.0" VERSION = "1.5.0"
settings = Settings() settings = Settings()
@@ -163,6 +164,8 @@ class ServiceModel(BaseModel):
port: int port: int
name: str name: str
ipv6: bool ipv6: bool
proto: str
ip_int: str
n_regex: int n_regex: int
n_packets: int n_packets: int
@@ -176,6 +179,8 @@ async def get_service_list(auth: bool = Depends(is_loggined)):
s.port port, s.port port,
s.name name, s.name name,
s.ipv6 ipv6, s.ipv6 ipv6,
s.proto proto,
s.ip_int ip_int,
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 FROM services s LEFT JOIN regexes r
@@ -192,6 +197,8 @@ async def get_service_by_id(service_id: str, auth: bool = Depends(is_loggined)):
s.port port, s.port port,
s.name name, s.name name,
s.ipv6 ipv6, s.ipv6 ipv6,
s.proto proto,
s.ip_int ip_int,
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 WHERE s.service_id = ? FROM services s LEFT JOIN regexes r WHERE s.service_id = ?
@@ -332,7 +339,6 @@ async def add_new_regex(form: RegexAddForm, auth: bool = Depends(is_loggined)):
class ServiceAddForm(BaseModel): class ServiceAddForm(BaseModel):
name: str name: str
port: int port: int
ipv6: bool
proto: str proto: str
ip_int: str ip_int: str
@@ -343,21 +349,22 @@ class ServiceAddResponse(BaseModel):
@app.post('/api/services/add', response_model=ServiceAddResponse) @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"""
if form.ipv6: ipv6 = None
if not checkIpv6(form.ip_int): try:
return {"status":"Invalid IPv6 address"} ip_int = ip_interface(form.ip_int)
else: ipv6 = ip_int.version == 6
if not checkIpv4(form.ip_int): form.ip_int = str(ip_int)
return {"status":"Invalid IPv4 address"} except ValueError:
return {"status":"Invalid address"}
if form.proto not in ["tcp", "udp"]: if form.proto not in ["tcp", "udp"]:
return {"status":"Invalid protocol"} return {"status":"Invalid protocol"}
srv_id = None srv_id = None
try: try:
srv_id = gen_service_id(db) srv_id = gen_service_id(db)
db.query("INSERT INTO services (service_id ,name, port, ipv6, status) VALUES (?, ?, ?, ?, ?)", db.query("INSERT INTO services (service_id ,name, port, ipv6, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?, ?)",
srv_id, refactor_name(form.name), form.port, form.ipv6, STATUS.STOP) srv_id, refactor_name(form.name), form.port, ipv6, STATUS.STOP, form.proto, form.ip_int)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
return {'status': 'Name or/and ports of the service has been already assigned'} return {'status': 'This type of service already exists'}
await firewall.reload() await firewall.reload()
await refresh_frontend() await refresh_frontend()
return {'status': 'ok', 'service_id': srv_id} return {'status': 'ok', 'service_id': srv_id}

View File

@@ -4,6 +4,7 @@ from pypacker.layer3 import ip, ip6
from pypacker.layer4 import tcp, udp from pypacker.layer4 import tcp, udp
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
import os, traceback, pcre, re import os, traceback, pcre, re
from ipaddress import ip_interface
QUEUE_BASE_NUM = 1000 QUEUE_BASE_NUM = 1000
@@ -17,10 +18,12 @@ class ProtoTypes:
class IPTables: class IPTables:
def __init__(self, ipv6=False): def __init__(self, ipv6=False, table="mangle"):
self.ipv6 = ipv6 self.ipv6 = ipv6
self.table = table
def command(self, params): def command(self, params):
params = ["-t", self.table] + 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(["ip6tables"]+params if self.ipv6 else ["iptables"]+params, stdout=PIPE, stderr=PIPE).communicate() return Popen(["ip6tables"]+params if self.ipv6 else ["iptables"]+params, stdout=PIPE, stderr=PIPE).communicate()
@@ -48,18 +51,12 @@ class IPTables:
self.command(["-F", str(name)]) self.command(["-F", str(name)])
def add_chain_to_input(self, name): def add_chain_to_input(self, name):
if not self.find_if_filter_exists("INPUT", str(name)): if not self.find_if_filter_exists("PREROUTING", str(name)):
self.command(["-I", "INPUT", "-j", str(name)]) self.command(["-I", "PREROUTING", "-j", str(name)])
if not self.find_if_filter_exists("DOCKER-USER", str(name)):
self.command(["-I", "DOCKER-USER", "-j", str(name)])
def add_chain_to_output(self, name): def add_chain_to_output(self, name):
if not self.find_if_filter_exists("OUTPUT", str(name)): if not self.find_if_filter_exists("POSTROUTING", str(name)):
self.command(["-I", "OUTPUT", "-j", str(name)]) self.command(["-I", "POSTROUTING", "-j", str(name)])
if not self.find_if_filter_exists("FORWARD", str(name)):
self.command(["-I", "FORWARD", "-j", str(name)])
if not self.find_if_filter_exists("DOCKER-USER", str(name)):
self.command(["-I", "DOCKER-USER", "-j", str(name)])
def find_if_filter_exists(self, type, target): def find_if_filter_exists(self, type, target):
for filter in self.list_filters(type): for filter in self.list_filters(type):
@@ -67,34 +64,39 @@ class IPTables:
return True return True
return False return False
def add_s_to_c(self, proto, port, queue_range): def add_s_to_c(self, queue_range, proto = None, port = None, ip_int = None):
init, end = queue_range init, end = queue_range
if init > end: init, end = end, init if init > end: init, end = end, init
self.command([ self.command(["-A", FilterTypes.OUTPUT,
"-A", FilterTypes.OUTPUT, "-p", str(proto), * (["-p", str(proto)] if proto else []),
"--sport", str(port), "-j", "NFQUEUE", * (["-s", str(ip_int)] if ip_int else []),
"--queue-num" if init == end else "--queue-balance", * (["--sport", str(port)] if port else []),
f"{init}" if init == end else f"{init}:{end}", "--queue-bypass" "-j", "NFQUEUE",
* (["--queue-num", f"{init}"] if init == end else ["--queue-balance", f"{init}:{end}"]),
"--queue-bypass"
]) ])
def add_c_to_s(self, proto, port, queue_range): def add_c_to_s(self, queue_range, proto = None, port = None, ip_int = None):
init, end = queue_range init, end = queue_range
if init > end: init, end = end, init if init > end: init, end = end, init
self.command([ self.command(["-A", FilterTypes.INPUT,
"-A", FilterTypes.INPUT, "-p", str(proto), * (["-p", str(proto)] if proto else []),
"--dport", str(port), "-j", "NFQUEUE", * (["-d", str(ip_int)] if ip_int else []),
"--queue-num" if init == end else "--queue-balance", * (["--dport", str(port)] if port else []),
f"{init}" if init == end else f"{init}:{end}", "--queue-bypass" "-j", "NFQUEUE",
* (["--queue-num", f"{init}"] if init == end else ["--queue-balance", f"{init}:{end}"]),
"--queue-bypass"
]) ])
class FiregexFilter(): class FiregexFilter():
def __init__(self, type, number, queue, proto, port, ipv6): def __init__(self, type, number, queue, proto, port, ipv6, ip_int):
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) self.iptable = IPTables(ipv6)
self.ip_int = str(ip_int)
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}>"
@@ -103,18 +105,17 @@ class FiregexFilter():
self.iptable.delete_command(self.type, self.id) self.iptable.delete_command(self.type, self.id)
class Interceptor: class Interceptor:
def __init__(self, iptables, c_to_s, s_to_c, proto, ipv6, port, n_threads): def __init__(self, iptables, ip_int, c_to_s, s_to_c, proto, ipv6, port, n_threads):
self.proto = proto self.proto = proto
self.ipv6 = ipv6 self.ipv6 = ipv6
self.itor_c_to_s, codes = self._start_queue(c_to_s, n_threads) self.itor_c_to_s, codes = self._start_queue(c_to_s, n_threads)
iptables.add_c_to_s(proto, port, codes) iptables.add_c_to_s(queue_range=codes, proto=proto, port=port, ip_int=ip_int)
self.itor_s_to_c, codes = self._start_queue(s_to_c, n_threads) self.itor_s_to_c, codes = self._start_queue(s_to_c, n_threads)
iptables.add_s_to_c(proto, port, codes) iptables.add_s_to_c(queue_range=codes, proto=proto, port=port, ip_int=ip_int)
def _start_queue(self,func,n_threads): def _start_queue(self,func,n_threads):
def func_wrap(ll_data, ll_proto_id, data, ctx, *args): def func_wrap(ll_data, ll_proto_id, data, ctx, *args):
pkt_parsed = ip6.IP6(data) if self.ipv6 else ip.IP(data) pkt_parsed = ip6.IP6(data) if self.ipv6 else ip.IP(data)
try: try:
level4 = None level4 = None
if self.proto == ProtoTypes.TCP: level4 = pkt_parsed[tcp.TCP].body_bytes if self.proto == ProtoTypes.TCP: level4 = pkt_parsed[tcp.TCP].body_bytes
@@ -152,9 +153,9 @@ class Interceptor:
class FiregexFilterManager: class FiregexFilterManager:
def __init__(self, ipv6): def __init__(self, srv):
self.ipv6 = ipv6 self.ipv6 = srv["ipv6"]
self.iptables = IPTables(ipv6) self.iptables = IPTables(self.ipv6)
self.iptables.create_chain(FilterTypes.INPUT) self.iptables.create_chain(FilterTypes.INPUT)
self.iptables.create_chain(FilterTypes.OUTPUT) self.iptables.create_chain(FilterTypes.OUTPUT)
self.iptables.add_chain_to_input(FilterTypes.INPUT) self.iptables.add_chain_to_input(FilterTypes.INPUT)
@@ -177,30 +178,32 @@ class FiregexFilterManager:
queue=queue_num, queue=queue_num,
proto=filter["prot"], proto=filter["prot"],
port=int(port[0]), port=int(port[0]),
ipv6=self.ipv6 ipv6=self.ipv6,
ip_int=filter["source"] if filter_type == FilterTypes.OUTPUT else filter["destination"]
)) ))
return res return res
def add(self, proto, port, func, n_threads = 1): def add(self, proto, port, ip_int, func):
for ele in self.get(): for ele in self.get():
if int(port) == ele.port: return None if int(port) == ele.port and proto == ele.proto and ip_interface(ip_int) == ip_interface(ele.ip_int):
return None
def c_to_s(pkt): return func(pkt, True) def c_to_s(pkt): return func(pkt, True)
def s_to_c(pkt): return func(pkt, False) def s_to_c(pkt): return func(pkt, False)
itor = Interceptor( self.iptables, itor = Interceptor( iptables=self.iptables, ip_int=ip_int,
c_to_s, s_to_c, c_to_s=c_to_s, s_to_c=s_to_c,
proto, self.ipv6, port, proto=proto, ipv6=self.ipv6, port=port,
int(os.getenv("N_THREADS_NFQUEUE","1"))) n_threads=int(os.getenv("N_THREADS_NFQUEUE","1")))
return itor return itor
def delete_all(self): def delete_all(self):
for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]: for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]:
self.iptables.flush_chain(filter_type) self.iptables.flush_chain(filter_type)
def delete_by_port(self, port): def delete_by_srv(self, srv):
for filter in self.get(): for filter in self.get():
if filter.port == int(port): if filter.port == int(srv["port"]) and filter.proto == srv["proto"] and ip_interface(filter.ip_int) == ip_interface(srv["ip_int"]):
filter.delete() filter.delete()
class Filter: class Filter:
@@ -224,10 +227,10 @@ 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, ipv6, filters=None): def __init__(self, srv, filters=None):
self.manager = FiregexFilterManager(ipv6) self.srv = srv
self.port = port self.manager = FiregexFilterManager(self.srv)
self.filters = filters if filters else [] self.filters: List[Filter] = filters if filters else []
self.interceptor = None self.interceptor = None
def set_filters(self, filters): def set_filters(self, filters):
@@ -239,7 +242,7 @@ class Proxy:
def start(self): def start(self):
if not self.interceptor: if not self.interceptor:
self.manager.delete_by_port(self.port) self.manager.delete_by_srv(self.srv)
def regex_filter(pkt, by_client): def regex_filter(pkt, by_client):
try: try:
for filter in self.filters: for filter in self.filters:
@@ -251,11 +254,11 @@ class Proxy:
except IndexError: pass except IndexError: pass
return True return True
self.interceptor = self.manager.add(ProtoTypes.TCP, self.port, regex_filter) self.interceptor = self.manager.add(self.srv["proto"], self.srv["port"], self.srv["ip_int"], regex_filter)
def stop(self): def stop(self):
self.manager.delete_by_port(self.port) self.manager.delete_by_srv(self.srv)
if self.interceptor: if self.interceptor:
self.interceptor.stop() self.interceptor.stop()
self.interceptor = None self.interceptor = None

View File

@@ -1,69 +1,27 @@
from hashlib import md5
import traceback import traceback
from typing import Dict from typing import Dict
from proxy import Filter, Proxy from proxy import Filter, Proxy
import os, sqlite3, socket, asyncio, re import os, sqlite3, socket, asyncio, re
import secrets import secrets, json
from base64 import b64decode from base64 import b64decode
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1")) LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
regex_ipv6 = r"^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$";
regex_ipv4 = r"^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$"
def checkIpv6(ip:str):
return bool(re.match(regex_ipv6, ip))
def checkIpv4(ip:str):
return bool(re.match(regex_ipv4, ip))
class SQLite(): class SQLite():
def __init__(self, db_name) -> None: def __init__(self, db_name) -> None:
self.conn = None self.conn = None
self.cur = None self.cur = None
self.db_name = db_name self.db_name = db_name
self.schema = {
def connect(self) -> None:
try:
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
except Exception:
with open(self.db_name, 'x'):
pass
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
self.conn.row_factory = dict_factory
def disconnect(self) -> None:
self.conn.close()
def create_schema(self, tables = {}) -> None:
cur = self.conn.cursor()
for t in tables:
cur.execute('''CREATE TABLE IF NOT EXISTS main.{}({});'''.format(t, ''.join([(c + ' ' + tables[t][c] + ', ') for c in tables[t]])[:-2]))
cur.close()
def query(self, query, *values):
cur = self.conn.cursor()
try:
cur.execute(query, values)
return cur.fetchall()
finally:
cur.close()
try: self.conn.commit()
except Exception: pass
def init(self):
self.connect()
self.create_schema({
'services': { 'services': {
'service_id': 'VARCHAR(100) PRIMARY KEY', '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)', 'port': 'INT NOT NULL CHECK(port > 0 and port < 65536)',
'name': 'VARCHAR(100) NOT NULL UNIQUE', 'name': 'VARCHAR(100) NOT NULL UNIQUE',
'ipv6': 'BOOLEAN NOT NULL CHECK (ipv6 IN (0, 1)) DEFAULT 0', 'ipv6': 'BOOLEAN NOT NULL CHECK (ipv6 IN (0, 1)) DEFAULT 0',
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
'ip_int': 'VARCHAR(100) NOT NULL',
}, },
'regexes': { 'regexes': {
'regex': 'TEXT NOT NULL', 'regex': 'TEXT NOT NULL',
@@ -80,9 +38,62 @@ class SQLite():
'key': 'VARCHAR(100) PRIMARY KEY', 'key': 'VARCHAR(100) PRIMARY KEY',
'value': 'VARCHAR(100) NOT NULL', 'value': 'VARCHAR(100) NOT NULL',
}, },
}) 'QUERY':[
self.query("CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (ipv6,port);") "CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (ipv6, port, ip_int, proto);",
self.query("CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);") "CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);"
]
}
self.DB_VER = md5(json.dumps(self.schema).encode()).hexdigest()
def connect(self) -> None:
try:
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
except Exception:
with open(self.db_name, 'x'):
pass
self.conn = sqlite3.connect(self.db_name, check_same_thread = False)
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
self.conn.row_factory = dict_factory
def disconnect(self) -> None:
if self.conn: self.conn.close()
def create_schema(self, tables = {}) -> None:
cur = self.conn.cursor()
for t in tables:
if t == "QUERY": continue
cur.execute('CREATE TABLE IF NOT EXISTS main.{}({});'.format(t, ''.join([(c + ' ' + tables[t][c] + ', ') for c in tables[t]])[:-2]))
if "QUERY" in tables: [cur.execute(qry) for qry in tables["QUERY"]]
cur.close()
def query(self, query, *values):
cur = self.conn.cursor()
try:
cur.execute(query, values)
return cur.fetchall()
finally:
cur.close()
try: self.conn.commit()
except Exception: pass
def delete(self):
self.disconnect()
os.remove(self.db_name)
def init(self):
self.connect()
try:
current_ver = self.query("SELECT value FROM keys_values WHERE key = 'DB_VERSION'")[0]['value']
if current_ver != self.DB_VER: raise Exception("DB_VERSION is not correct")
except Exception:
self.delete()
self.connect()
self.create_schema(self.schema)
self.query("INSERT INTO keys_values (key, value) VALUES ('DB_VERSION', ?)", self.DB_VER)
class KeyValueStorage: class KeyValueStorage:
def __init__(self, db): def __init__(self, db):
@@ -108,11 +119,10 @@ class STATUS:
class ServiceNotFoundException(Exception): pass class ServiceNotFoundException(Exception): pass
class ServiceManager: class ServiceManager:
def __init__(self, id, port, ipv6, db): def __init__(self, srv, db):
self.id = id self.srv = srv
self.port = port
self.db = db self.db = db
self.proxy = Proxy(port, ipv6) self.proxy = Proxy(srv)
self.status = STATUS.STOP self.status = STATUS.STOP
self.filters = {} self.filters = {}
self._update_filters_from_db() self._update_filters_from_db()
@@ -122,10 +132,10 @@ class ServiceManager:
def _update_filters_from_db(self): def _update_filters_from_db(self):
res = self.db.query(""" res = self.db.query("""
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_id = ? AND active=1; FROM regexes WHERE service_id = ? AND active=1;
""", self.id) """, self.srv["service_id"])
#Filter check #Filter check
old_filters = set(self.filters.keys()) old_filters = set(self.filters.keys())
@@ -151,7 +161,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 service_id = ?;", status, self.id) self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.srv["service_id"])
async def next(self,to): async def next(self,to):
async with self.lock: async with self.lock:
@@ -216,13 +226,14 @@ 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 service_id, port, status, ipv6 FROM services;'): for srv in self.db.query('SELECT * 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: srv_id = srv["service_id"]
if srv_id in self.proxy_table:
continue continue
self.proxy_table[srv_id] = ServiceManager(srv_id, srv_port, srv_ipv6, self.db) self.proxy_table[srv_id] = ServiceManager(srv, self.db)
await self.proxy_table[srv_id].next(req_status) await self.proxy_table[srv_id].next(srv["status"])
async def _stats_updater(self, callback): async def _stats_updater(self, callback):
try: try:

View File

@@ -1,13 +1,13 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.08225a85.css", "main.css": "/static/css/main.08225a85.css",
"main.js": "/static/js/main.3f472f16.js", "main.js": "/static/js/main.0e7d88b5.js",
"index.html": "/index.html", "index.html": "/index.html",
"main.08225a85.css.map": "/static/css/main.08225a85.css.map", "main.08225a85.css.map": "/static/css/main.08225a85.css.map",
"main.3f472f16.js.map": "/static/js/main.3f472f16.js.map" "main.0e7d88b5.js.map": "/static/js/main.0e7d88b5.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.08225a85.css", "static/css/main.08225a85.css",
"static/js/main.3f472f16.js" "static/js/main.0e7d88b5.js"
] ]
} }

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.3f472f16.js"></script><link href="/static/css/main.08225a85.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.0e7d88b5.js"></script><link href="/static/css/main.08225a85.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,70 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* React Router v6.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

View File

@@ -29,7 +29,7 @@
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "BROWSER=none react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"

View File

@@ -41,8 +41,7 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
const submitRequest = ({ name, port, autostart, proto, ip_int }:ServiceAddForm) =>{ const submitRequest = ({ name, port, autostart, proto, ip_int }:ServiceAddForm) =>{
setSubmitLoading(true) setSubmitLoading(true)
const ipv6 = ip_int.match(regex_ipv4)?false:true addservice({name, port, proto, ip_int }).then( res => {
addservice({name, port, ipv6, proto, ip_int }).then( res => {
if (res.status === "ok" && res.service_id){ if (res.status === "ok" && res.service_id){
setSubmitLoading(false) setSubmitLoading(false)
close(); close();
@@ -69,7 +68,7 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
<TextInput <TextInput
label="Public IP Interface (ipv4/ipv6 + CIDR allowed)" label="Public IP Interface (ipv4/ipv6 + CIDR allowed)"
placeholder="10.40.20.0/8" placeholder="10.1.1.0/24"
{...form.getInputProps('ip_int')} {...form.getInputProps('ip_int')}
/> />
@@ -85,6 +84,13 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
<Space h="md" /> <Space h="md" />
<div className='center-flex'>
<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })}
/>
<div className="flex-spacer"></div>
<SegmentedControl <SegmentedControl
data={[ data={[
{ label: 'TCP', value: 'tcp' }, { label: 'TCP', value: 'tcp' },
@@ -92,13 +98,7 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
]} ]}
{...form.getInputProps('proto')} {...form.getInputProps('proto')}
/> />
</div>
<Space h="xl" />
<Switch
label="Auto-Start Service"
{...form.getInputProps('autostart', { 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>

View File

@@ -98,7 +98,7 @@ function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void })
<Space h="xs" /> <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" /> <Space h="xs" />
<Badge color={service.ipv6?"pink":"cyan"} radius="sm" size="md" variant="filled">Protocol: {service.ipv6?"IPv6":"IPv4"}</Badge> <Badge color={service.ipv6?"pink":"cyan"} radius="sm" size="md" variant="filled">{service.ip_int} on {service.proto}</Badge>
</div> </div>
<MediaQuery largerThan="md" styles={{ display: 'none' }}> <MediaQuery largerThan="md" styles={{ display: 'none' }}>
<div className='flex-spacer' /> <div className='flex-spacer' />

View File

@@ -10,6 +10,8 @@ export type Service = {
status:string, status:string,
port:number, port:number,
ipv6:boolean, ipv6:boolean,
proto: string,
ip_int: string,
n_packets:number, n_packets:number,
n_regex:number, n_regex:number,
} }
@@ -17,7 +19,6 @@ export type Service = {
export type ServiceAddForm = { export type ServiceAddForm = {
name:string, name:string,
port:number, port:number,
ipv6:boolean,
proto:string, proto:string,
ip_int:string, ip_int:string,
} }

View File

@@ -18,7 +18,9 @@ function ServiceDetails() {
n_regex:0, n_regex:0,
name:"", name:"",
ipv6:false, ipv6:false,
status:"🤔" status:"🤔",
ip_int: "",
proto: "tcp",
}) })
const [regexesList, setRegexesList] = useState<RegexFilter[]>([]) const [regexesList, setRegexesList] = useState<RegexFilter[]>([])