IP interface and Protocol + fix iptables
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
135
backend/utils.py
135
backend/utils.py
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
3
frontend/build/static/js/main.0e7d88b5.js
Normal file
3
frontend/build/static/js/main.0e7d88b5.js
Normal file
File diff suppressed because one or more lines are too long
70
frontend/build/static/js/main.0e7d88b5.js.LICENSE.txt
Normal file
70
frontend/build/static/js/main.0e7d88b5.js.LICENSE.txt
Normal 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.
|
||||||
|
*/
|
||||||
1
frontend/build/static/js/main.0e7d88b5.js.map
Normal file
1
frontend/build/static/js/main.0e7d88b5.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -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"
|
||||||
|
|||||||
@@ -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,20 +84,21 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
|||||||
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<SegmentedControl
|
|
||||||
data={[
|
|
||||||
{ label: 'TCP', value: 'tcp' },
|
|
||||||
{ label: 'UDP', value: 'udp' },
|
|
||||||
]}
|
|
||||||
{...form.getInputProps('proto')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Space h="xl" />
|
<div className='center-flex'>
|
||||||
|
<Switch
|
||||||
<Switch
|
label="Auto-Start Service"
|
||||||
label="Auto-Start Service"
|
{...form.getInputProps('autostart', { type: 'checkbox' })}
|
||||||
{...form.getInputProps('autostart', { type: 'checkbox' })}
|
/>
|
||||||
/>
|
<div className="flex-spacer"></div>
|
||||||
|
<SegmentedControl
|
||||||
|
data={[
|
||||||
|
{ label: 'TCP', value: 'tcp' },
|
||||||
|
{ label: 'UDP', value: 'udp' },
|
||||||
|
]}
|
||||||
|
{...form.getInputProps('proto')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
|
|||||||
@@ -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' />
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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[]>([])
|
||||||
|
|||||||
Reference in New Issue
Block a user