diff --git a/Dockerfile b/Dockerfile
index 7f7994d..85a320c 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -38,6 +38,7 @@ RUN g++ binsrc/proxy.cpp -o modules/proxy -O3 -pthread -lboost_system -lboost_th
COPY ./backend/ /execute/
COPY --from=frontend /app/build/ ./frontend/
-ENTRYPOINT ["/bin/sh", "/execute/docker-entrypoint.sh"]
+
+CMD ["/bin/sh", "/execute/docker-entrypoint.sh"]
diff --git a/README.md b/README.md
index d1cdc4c..5bfda25 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,6 @@ Initiially the project was based only on regex filters, and also now the main fu
## Next points
-- Create hijacking port to proxy
- Explanation about tools in the dedicated pages making them more user-friendly
- buffering the TCP and(/or) the UDP stream to avoid to bypass the proxy dividing the information in more packets
- Adding new section with "general firewall rules" to manage "simple" TCP traffic rules graphically and through nftables
diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh
index 6d7520a..a44a0b0 100644
--- a/backend/docker-entrypoint.sh
+++ b/backend/docker-entrypoint.sh
@@ -2,8 +2,7 @@
chown nobody:nobody -R /execute/
-capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
- --keep=1 --user=nobody --addamb=cap_net_admin -- \
- -c "python3 /execute/app.py DOCKER"
+exec capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
+ --keep=1 --user=nobody --addamb=cap_net_admin -- -c "python3 /execute/app.py DOCKER"
diff --git a/backend/modules/nfregex/firegex.py b/backend/modules/nfregex/firegex.py
index 15d5d40..0753e0f 100644
--- a/backend/modules/nfregex/firegex.py
+++ b/backend/modules/nfregex/firegex.py
@@ -1,10 +1,12 @@
from typing import Dict, List, Set
-from utils.firegextables import FiregexFilter, FiregexTables
-from utils import ip_parse, ip_family, run_func
+from modules.nfregex.nftables import FiregexTables
+from utils import ip_parse, run_func
from modules.nfregex.models import Service, Regex
import re, os, asyncio
import traceback
+nft = FiregexTables()
+
class RegexFilter:
def __init__(
self, regex,
@@ -52,7 +54,7 @@ class RegexFilter:
class FiregexInterceptor:
def __init__(self):
- self.filter:FiregexFilter
+ self.srv:Service
self.filter_map_lock:asyncio.Lock
self.filter_map: Dict[str, RegexFilter]
self.regex_filters: Set[RegexFilter]
@@ -61,16 +63,14 @@ class FiregexInterceptor:
self.update_task: asyncio.Task
@classmethod
- async def start(cls, filter: FiregexFilter):
+ async def start(cls, srv: Service):
self = cls()
- self.filter = filter
+ self.srv = srv
self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock()
input_range, output_range = await self._start_binary()
self.update_task = asyncio.create_task(self.update_blocked())
- if not filter in FiregexTables().get():
- FiregexTables().add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
- FiregexTables().add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
+ nft.add(self.srv, input_range, output_range)
return self
async def _start_binary(self):
@@ -139,8 +139,3 @@ class FiregexInterceptor:
except Exception: pass
return res
-def delete_by_srv(srv:Service):
- nft = FiregexTables()
- for filter in nft.get():
- if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int):
- nft.cmd({"delete":{"rule": {"handle": filter.id, "table": nft.table_name, "chain": filter.target, "family": "inet"}}})
\ No newline at end of file
diff --git a/backend/modules/nfregex/firewall.py b/backend/modules/nfregex/firewall.py
index 03ba62b..18544f6 100644
--- a/backend/modules/nfregex/firewall.py
+++ b/backend/modules/nfregex/firewall.py
@@ -1,6 +1,7 @@
import asyncio
from typing import Dict
-from modules.nfregex.firegex import FiregexFilter, FiregexInterceptor, FiregexTables, RegexFilter, delete_by_srv
+from modules.nfregex.firegex import FiregexInterceptor, RegexFilter
+from modules.nfregex.nftables import FiregexTables, FiregexFilter
from modules.nfregex.models import Regex, Service
from utils.sqlite import SQLite
@@ -8,38 +9,40 @@ class STATUS:
STOP = "stop"
ACTIVE = "active"
+nft = FiregexTables()
+
class FirewallManager:
def __init__(self, db:SQLite):
self.db = db
- self.proxy_table: Dict[str, ServiceManager] = {}
+ self.service_table: Dict[str, ServiceManager] = {}
self.lock = asyncio.Lock()
async def close(self):
- for key in list(self.proxy_table.keys()):
+ for key in list(self.service_table.keys()):
await self.remove(key)
async def remove(self,srv_id):
async with self.lock:
- if srv_id in self.proxy_table:
- await self.proxy_table[srv_id].next(STATUS.STOP)
- del self.proxy_table[srv_id]
+ if srv_id in self.service_table:
+ await self.service_table[srv_id].next(STATUS.STOP)
+ del self.service_table[srv_id]
async def init(self):
- FiregexTables().init()
+ nft.init()
await self.reload()
async def reload(self):
async with self.lock:
for srv in self.db.query('SELECT * FROM services;'):
srv = Service.from_dict(srv)
- if srv.id in self.proxy_table:
+ if srv.id in self.service_table:
continue
- self.proxy_table[srv.id] = ServiceManager(srv, self.db)
- await self.proxy_table[srv.id].next(srv.status)
+ self.service_table[srv.id] = ServiceManager(srv, self.db)
+ await self.service_table[srv.id].next(srv.status)
def get(self,srv_id):
- if srv_id in self.proxy_table:
- return self.proxy_table[srv_id]
+ if srv_id in self.service_table:
+ return self.service_table[srv_id]
else:
raise ServiceNotFoundException()
@@ -94,13 +97,13 @@ class ServiceManager:
async def start(self):
if not self.interceptor:
- delete_by_srv(self.srv)
- self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int))
+ nft.delete(self.srv)
+ self.interceptor = await FiregexInterceptor.start(self.srv)
await self._update_filters_from_db()
self._set_status(STATUS.ACTIVE)
async def stop(self):
- delete_by_srv(self.srv)
+ nft.delete(self.srv)
if self.interceptor:
await self.interceptor.stop()
self.interceptor = None
diff --git a/backend/modules/nfregex/models.py b/backend/modules/nfregex/models.py
index d365412..e020d87 100644
--- a/backend/modules/nfregex/models.py
+++ b/backend/modules/nfregex/models.py
@@ -11,7 +11,14 @@ class Service:
@classmethod
def from_dict(cls, var: dict):
- return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"])
+ return cls(
+ id=var["service_id"],
+ status=var["status"],
+ port=var["port"],
+ name=var["name"],
+ proto=var["proto"],
+ ip_int=var["ip_int"]
+ )
class Regex:
@@ -27,4 +34,13 @@ class Regex:
@classmethod
def from_dict(cls, var: dict):
- return cls(id=var["regex_id"], regex=base64.b64decode(var["regex"]), mode=var["mode"], service_id=var["service_id"], is_blacklist=var["is_blacklist"], blocked_packets=var["blocked_packets"], is_case_sensitive=var["is_case_sensitive"], active=var["active"])
\ No newline at end of file
+ return cls(
+ id=var["regex_id"],
+ regex=base64.b64decode(var["regex"]),
+ mode=var["mode"],
+ service_id=var["service_id"],
+ is_blacklist=var["is_blacklist"],
+ blocked_packets=var["blocked_packets"],
+ is_case_sensitive=var["is_case_sensitive"],
+ active=var["active"]
+ )
\ No newline at end of file
diff --git a/backend/modules/nfregex/nftables.py b/backend/modules/nfregex/nftables.py
new file mode 100644
index 0000000..47b77c2
--- /dev/null
+++ b/backend/modules/nfregex/nftables.py
@@ -0,0 +1,109 @@
+from typing import List
+from modules.nfregex.models import Service
+from utils import ip_parse, ip_family, NFTableManager, nftables_int_to_json
+
+class FiregexFilter:
+ def __init__(self, proto:str, port:int, ip_int:str, target:str, id:int):
+ self.id = id
+ self.target = target
+ self.proto = proto
+ self.port = int(port)
+ self.ip_int = str(ip_int)
+
+ def __eq__(self, o: object) -> bool:
+ if isinstance(o, FiregexFilter):
+ return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
+ elif isinstance(o, Service):
+ return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
+ return False
+
+class FiregexTables(NFTableManager):
+ input_chain = "nfregex_input"
+ output_chain = "nfregex_output"
+
+ def __init__(self):
+ super().__init__([
+ {"add":{"chain":{
+ "family":"inet",
+ "table":self.table_name,
+ "name":self.input_chain,
+ "type":"filter",
+ "hook":"prerouting",
+ "prio":-150,
+ "policy":"accept"
+ }}},
+ {"add":{"chain":{
+ "family":"inet",
+ "table":self.table_name,
+ "name":self.output_chain,
+ "type":"filter",
+ "hook":"postrouting",
+ "prio":-150,
+ "policy":"accept"
+ }}}
+ ],[
+ {"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.input_chain}}},
+ {"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.input_chain}}},
+ {"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
+ {"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
+ ])
+
+ def add(self, srv:Service, queue_range_input, queue_range_output):
+
+ for ele in self.get():
+ if ele.__eq__(srv): return
+
+ init, end = queue_range_output
+ if init > end: init, end = end, init
+ self.cmd({ "insert":{ "rule": {
+ "family": "inet",
+ "table": self.table_name,
+ "chain": self.output_chain,
+ "expr": [
+ {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'saddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
+ {'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.port)}},
+ {"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
+ ]
+ }}})
+
+ init, end = queue_range_input
+ if init > end: init, end = end, init
+ self.cmd({"insert":{"rule":{
+ "family": "inet",
+ "table": self.table_name,
+ "chain": self.input_chain,
+ "expr": [
+ {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'daddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
+ {'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "dport"}}, "op": "==", "right": int(srv.port)}},
+ {"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
+ ]
+ }}})
+
+
+ def get(self) -> List[FiregexFilter]:
+ res = []
+ for filter in self.list_rules(tables=[self.table_name], chains=[self.input_chain,self.output_chain]):
+ ip_int = None
+ if isinstance(filter["expr"][0]["match"]["right"],str):
+ ip_int = str(ip_parse(filter["expr"][0]["match"]["right"]))
+ else:
+ ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}'
+ res.append(FiregexFilter(
+ target=filter["chain"],
+ id=int(filter["handle"]),
+ proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
+ port=filter["expr"][1]["match"]["right"],
+ ip_int=ip_int
+ ))
+ return res
+
+ def delete(self, srv:Service):
+ for filter in self.get():
+ if filter.__eq__(srv):
+ self.cmd({ "delete":{ "rule": {
+ "family": "inet",
+ "table": self.table_name,
+ "chain": filter.target,
+ "handle": filter.id
+ }}})
+
\ No newline at end of file
diff --git a/backend/modules/porthijack/__init__.py b/backend/modules/porthijack/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/modules/porthijack/firewall.py b/backend/modules/porthijack/firewall.py
new file mode 100644
index 0000000..b1d21a4
--- /dev/null
+++ b/backend/modules/porthijack/firewall.py
@@ -0,0 +1,77 @@
+import asyncio
+from typing import Dict
+from modules.porthijack.nftables import FiregexTables
+from modules.porthijack.models import Service
+from utils.sqlite import SQLite
+
+nft = FiregexTables()
+
+class FirewallManager:
+ def __init__(self, db:SQLite):
+ self.db = db
+ self.service_table: Dict[str, ServiceManager] = {}
+ self.lock = asyncio.Lock()
+
+ async def close(self):
+ for key in list(self.service_table.keys()):
+ await self.remove(key)
+
+ async def remove(self,srv_id):
+ async with self.lock:
+ if srv_id in self.service_table:
+ await self.service_table[srv_id].disable()
+ del self.service_table[srv_id]
+
+ async def init(self):
+ FiregexTables().init()
+ await self.reload()
+
+ async def reload(self):
+ async with self.lock:
+ for srv in self.db.query('SELECT * FROM services;'):
+ srv = Service.from_dict(srv)
+ if srv.service_id in self.service_table:
+ continue
+ self.service_table[srv.service_id] = ServiceManager(srv, self.db)
+ if srv.active:
+ await self.service_table[srv.service_id].enable()
+
+ def get(self,srv_id):
+ if srv_id in self.service_table:
+ return self.service_table[srv_id]
+ else:
+ raise ServiceNotFoundException()
+
+class ServiceNotFoundException(Exception): pass
+
+class ServiceManager:
+ def __init__(self, srv: Service, db):
+ self.srv = srv
+ self.db = db
+ self.active = False
+ self.lock = asyncio.Lock()
+
+ async def enable(self):
+ if not self.active:
+ async with self.lock:
+ nft.delete(self.srv)
+ nft.add(self.srv)
+ self._set_status(True)
+
+ async def disable(self):
+ if self.active:
+ async with self.lock:
+ nft.delete(self.srv)
+ self._set_status(False)
+
+ async def refresh(self, srv:Service):
+ self.srv = srv
+ if self.active: await self.restart()
+
+ def _set_status(self,active):
+ self.active = active
+ self.db.query("UPDATE services SET active = ? WHERE service_id = ?;", active, self.srv.service_id)
+
+ async def restart(self):
+ await self.disable()
+ await self.enable()
\ No newline at end of file
diff --git a/backend/modules/porthijack/models.py b/backend/modules/porthijack/models.py
new file mode 100644
index 0000000..a89e6d6
--- /dev/null
+++ b/backend/modules/porthijack/models.py
@@ -0,0 +1,23 @@
+class Service:
+ def __init__(self, service_id: str, active: bool, public_port: int, proxy_port: int, name: str, proto: str, ip_src: str, ip_dst:str):
+ self.service_id = service_id
+ self.active = active
+ self.public_port = public_port
+ self.proxy_port = proxy_port
+ self.name = name
+ self.proto = proto
+ self.ip_src = ip_src
+ self.ip_dst = ip_dst
+
+ @classmethod
+ def from_dict(cls, var: dict):
+ return cls(
+ service_id=var["service_id"],
+ active=var["active"],
+ public_port=var["public_port"],
+ proxy_port=var["proxy_port"],
+ name=var["name"],
+ proto=var["proto"],
+ ip_src=var["ip_src"],
+ ip_dst=var["ip_dst"]
+ )
diff --git a/backend/modules/porthijack/nftables.py b/backend/modules/porthijack/nftables.py
new file mode 100644
index 0000000..92255f3
--- /dev/null
+++ b/backend/modules/porthijack/nftables.py
@@ -0,0 +1,105 @@
+from typing import List
+from modules.porthijack.models import Service
+from utils import addr_parse, ip_parse, ip_family, NFTableManager, nftables_json_to_int
+
+class FiregexHijackRule():
+ def __init__(self, proto:str, public_port:int,proxy_port:int, ip_src:str, ip_dst:str, target:str, id:int):
+ self.id = id
+ self.target = target
+ self.proto = proto
+ self.public_port = public_port
+ self.proxy_port = proxy_port
+ self.ip_src = str(ip_src)
+ self.ip_dst = str(ip_dst)
+
+ def __eq__(self, o: object) -> bool:
+ if isinstance(o, FiregexHijackRule):
+ return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_src) == ip_parse(o.ip_src)
+ elif isinstance(o, Service):
+ return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_src) == ip_parse(o.ip_src)
+ return False
+
+class FiregexTables(NFTableManager):
+ prerouting_porthijack = "prerouting_porthijack"
+ postrouting_porthijack = "postrouting_porthijack"
+
+ def __init__(self):
+ super().__init__([
+ {"add":{"chain":{
+ "family":"inet",
+ "table":self.table_name,
+ "name":self.prerouting_porthijack,
+ "type":"filter",
+ "hook":"prerouting",
+ "prio":-300,
+ "policy":"accept"
+ }}},
+ {"add":{"chain":{
+ "family":"inet",
+ "table":self.table_name,
+ "name":self.postrouting_porthijack,
+ "type":"filter",
+ "hook":"postrouting",
+ "prio":-300,
+ "policy":"accept"
+ }}}
+ ],[
+ {"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.prerouting_porthijack}}},
+ {"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.prerouting_porthijack}}},
+ {"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.postrouting_porthijack}}},
+ {"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.postrouting_porthijack}}}
+ ])
+
+ def add(self, srv:Service):
+
+ for ele in self.get():
+ if ele.__eq__(srv): return
+
+ self.cmd({ "insert":{ "rule": {
+ "family": "inet",
+ "table": self.table_name,
+ "chain": self.prerouting_porthijack,
+ "expr": [
+ {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'daddr'}}, 'op': '==', 'right': addr_parse(srv.ip_src)}},
+ {'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'op': '==', 'right': int(srv.public_port)}},
+ {'mangle': {'key': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'value': int(srv.proxy_port)}},
+ {'mangle': {'key': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'daddr'}}, 'value': addr_parse(srv.ip_dst)}}
+ ]
+ }}})
+ self.cmd({ "insert":{ "rule": {
+ "family": "inet",
+ "table": self.table_name,
+ "chain": self.postrouting_porthijack,
+ "expr": [
+ {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'saddr'}}, 'op': '==', 'right': addr_parse(srv.ip_dst)}},
+ {'match': {'left': { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.proxy_port)}},
+ {'mangle': {'key': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'value': int(srv.public_port)}},
+ {'mangle': {'key': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'saddr'}}, 'value': addr_parse(srv.ip_src)}}
+ ]
+ }}})
+
+
+ def get(self) -> List[FiregexHijackRule]:
+ res = []
+ for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]):
+ filter["expr"][0]["match"]["right"]
+ res.append(FiregexHijackRule(
+ target=filter["chain"],
+ id=int(filter["handle"]),
+ proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
+ public_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.prerouting_porthijack else filter["expr"][2]["mangle"]["value"],
+ proxy_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.postrouting_porthijack else filter["expr"][2]["mangle"]["value"],
+ ip_src=nftables_json_to_int(filter["expr"][0]["match"]["right"]) if filter["chain"] == self.prerouting_porthijack else nftables_json_to_int(filter["expr"][3]["mangle"]["value"]),
+ ip_dst=nftables_json_to_int(filter["expr"][0]["match"]["right"]) if filter["chain"] == self.postrouting_porthijack else nftables_json_to_int(filter["expr"][3]["mangle"]["value"]),
+ ))
+ return res
+
+ def delete(self, srv:Service):
+ for filter in self.get():
+ if filter.__eq__(srv):
+ self.cmd({ "delete":{ "rule": {
+ "family": "inet",
+ "table": self.table_name,
+ "chain": filter.target,
+ "handle": filter.id
+ }}})
\ No newline at end of file
diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py
index 48c6e5e..91131b1 100644
--- a/backend/routers/nfregex.py
+++ b/backend/routers/nfregex.py
@@ -5,7 +5,7 @@ import sqlite3
from typing import List, Union
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
-from modules.nfregex.firegex import FiregexTables
+from modules.nfregex.nftables import FiregexTables
from modules.nfregex.firewall import STATUS, FirewallManager
from utils.sqlite import SQLite
from utils import ip_parse, refactor_name, refresh_frontend
@@ -145,7 +145,7 @@ async def get_service_list():
""")
@app.get('/service/{service_id}', response_model=ServiceModel)
-async def get_service_by_id(service_id: str, ):
+async def get_service_by_id(service_id: str):
"""Get info about a specific service using his id"""
res = db.query("""
SELECT
@@ -164,21 +164,21 @@ async def get_service_by_id(service_id: str, ):
return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
-async def service_stop(service_id: str, ):
+async def service_stop(service_id: str):
"""Request the stop of a specific service"""
await firewall.get(service_id).next(STATUS.STOP)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
-async def service_start(service_id: str, ):
+async def service_start(service_id: str):
"""Request the start of a specific service"""
await firewall.get(service_id).next(STATUS.ACTIVE)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
-async def service_delete(service_id: str, ):
+async def service_delete(service_id: str):
"""Request the deletion of a specific service"""
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
@@ -187,7 +187,7 @@ async def service_delete(service_id: str, ):
return {'status': 'ok'}
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
-async def service_rename(service_id: str, form: RenameForm, ):
+async def service_rename(service_id: str, form: RenameForm):
"""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!'}
@@ -199,7 +199,7 @@ async def service_rename(service_id: str, form: RenameForm, ):
return {'status': 'ok'}
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
-async def get_service_regexe_list(service_id: str, ):
+async def get_service_regexe_list(service_id: str):
"""Get the list of the regexes of a service"""
return db.query("""
SELECT
@@ -209,7 +209,7 @@ async def get_service_regexe_list(service_id: str, ):
""", service_id)
@app.get('/regex/{regex_id}', response_model=RegexModel)
-async def get_regex_by_id(regex_id: int, ):
+async def get_regex_by_id(regex_id: int):
"""Get regex info using his id"""
res = db.query("""
SELECT
@@ -221,7 +221,7 @@ async def get_regex_by_id(regex_id: int, ):
return res[0]
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
-async def regex_delete(regex_id: int, ):
+async def regex_delete(regex_id: int):
"""Delete a regex using his id"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
@@ -232,7 +232,7 @@ async def regex_delete(regex_id: int, ):
return {'status': 'ok'}
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
-async def regex_enable(regex_id: int, ):
+async def regex_enable(regex_id: int):
"""Request the enabling of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
@@ -242,7 +242,7 @@ async def regex_enable(regex_id: int, ):
return {'status': 'ok'}
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
-async def regex_disable(regex_id: int, ):
+async def regex_disable(regex_id: int):
"""Request the deactivation of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
@@ -252,7 +252,7 @@ async def regex_disable(regex_id: int, ):
return {'status': 'ok'}
@app.post('/regexes/add', response_model=StatusMessageModel)
-async def add_new_regex(form: RegexAddForm, ):
+async def add_new_regex(form: RegexAddForm):
"""Add a new regex"""
try:
re.compile(b64decode(form.regex))
@@ -269,7 +269,7 @@ async def add_new_regex(form: RegexAddForm, ):
return {'status': 'ok'}
@app.post('/services/add', response_model=ServiceAddResponse)
-async def add_new_service(form: ServiceAddForm, ):
+async def add_new_service(form: ServiceAddForm):
"""Add a new service"""
try:
form.ip_int = ip_parse(form.ip_int)
diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py
new file mode 100644
index 0000000..16b92ba
--- /dev/null
+++ b/backend/routers/porthijack.py
@@ -0,0 +1,196 @@
+import secrets
+import sqlite3
+from typing import List, Union
+from fastapi import APIRouter, HTTPException
+from pydantic import BaseModel
+from modules.porthijack.models import Service
+from utils.sqlite import SQLite
+from utils import addr_parse, ip_family, refactor_name, refresh_frontend
+from utils.models import ResetRequest, StatusMessageModel
+from modules.porthijack.nftables import FiregexTables
+from modules.porthijack.firewall import FirewallManager
+
+class ServiceModel(BaseModel):
+ service_id: str
+ active: bool
+ public_port: int
+ proxy_port: int
+ name: str
+ proto: str
+ ip_src: str
+ ip_dst: str
+
+class RenameForm(BaseModel):
+ name:str
+
+class ServiceAddForm(BaseModel):
+ name: str
+ public_port: int
+ proxy_port: int
+ proto: str
+ ip_src: str
+ ip_dst: str
+
+class ServiceAddResponse(BaseModel):
+ status:str
+ service_id: Union[None,str]
+
+class GeneralStatModel(BaseModel):
+ services: int
+
+app = APIRouter()
+
+db = SQLite('db/port-hijacking.db', {
+ 'services': {
+ 'service_id': 'VARCHAR(100) PRIMARY KEY',
+ 'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
+ 'public_port': 'INT NOT NULL CHECK(public_port > 0 and public_port < 65536)',
+ 'proxy_port': 'INT NOT NULL CHECK(proxy_port > 0 and proxy_port < 65536 and proxy_port != public_port)',
+ 'name': 'VARCHAR(100) NOT NULL UNIQUE',
+ 'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
+ 'ip_src': 'VARCHAR(100) NOT NULL',
+ 'ip_dst': 'VARCHAR(100) NOT NULL',
+ },
+ 'QUERY':[
+ "CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_src, proto);"
+ ]
+})
+
+async def reset(params: ResetRequest):
+ if not params.delete:
+ db.backup()
+ await firewall.close()
+ FiregexTables().reset()
+ if params.delete:
+ db.delete()
+ db.init()
+ else:
+ db.restore()
+ await firewall.init()
+
+
+async def startup():
+ db.init()
+ await firewall.init()
+
+async def shutdown():
+ db.backup()
+ await firewall.close()
+ db.disconnect()
+ db.restore()
+
+def gen_service_id():
+ while True:
+ res = secrets.token_hex(8)
+ if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0:
+ break
+ return res
+
+firewall = FirewallManager(db)
+
+@app.get('/stats', response_model=GeneralStatModel)
+async def get_general_stats():
+ """Get firegex general status about services"""
+ return db.query("""
+ SELECT
+ (SELECT COUNT(*) FROM services) services
+ """)[0]
+
+@app.get('/services', response_model=List[ServiceModel])
+async def get_service_list():
+ """Get the list of existent firegex services"""
+ return db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services;")
+
+@app.get('/service/{service_id}', response_model=ServiceModel)
+async def get_service_by_id(service_id: str):
+ """Get info about a specific service using his id"""
+ res = db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services WHERE service_id = ?;", service_id)
+ if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
+ return res[0]
+
+@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
+async def service_stop(service_id: str):
+ """Request the stop of a specific service"""
+ await firewall.get(service_id).disable()
+ await refresh_frontend()
+ return {'status': 'ok'}
+
+@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
+async def service_start(service_id: str):
+ """Request the start of a specific service"""
+ await firewall.get(service_id).enable()
+ await refresh_frontend()
+ return {'status': 'ok'}
+
+@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
+async def service_delete(service_id: str):
+ """Request the deletion of a specific service"""
+ db.query('DELETE FROM services WHERE service_id = ?;', service_id)
+ await firewall.remove(service_id)
+ await refresh_frontend()
+ return {'status': 'ok'}
+
+@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
+async def service_rename(service_id: str, form: RenameForm):
+ """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!'}
+ try:
+ db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
+ except sqlite3.IntegrityError:
+ return {'status': 'This name is already used'}
+ await refresh_frontend()
+ return {'status': 'ok'}
+
+class ChangeDestination(BaseModel):
+ ip_dst: str
+ proxy_port: int
+
+@app.post('/service/{service_id}/change-destination', response_model=StatusMessageModel)
+async def service_change_destination(service_id: str, form: ChangeDestination):
+ """Request to change the proxy destination of the service"""
+
+ try:
+ form.ip_dst = addr_parse(form.ip_dst)
+ except ValueError:
+ return {"status":"Invalid address"}
+ srv = Service.from_dict(db.query('SELECT * FROM services WHERE service_id = ?;', service_id)[0])
+ if ip_family(form.ip_dst) != ip_family(srv.ip_src):
+ return {'status': 'The destination ip is not of the same family as the source ip'}
+ try:
+ db.query('UPDATE services SET proxy_port=?, ip_dst=? WHERE service_id = ?;', form.proxy_port, form.ip_dst, service_id)
+ except sqlite3.IntegrityError:
+ return {'status': 'Invalid proxy port or service'}
+
+ srv.ip_dst = form.ip_dst
+ srv.proxy_port = form.proxy_port
+ await firewall.get(service_id).refresh(srv)
+
+ await refresh_frontend()
+ return {'status': 'ok'}
+
+@app.post('/services/add', response_model=ServiceAddResponse)
+async def add_new_service(form: ServiceAddForm):
+ """Add a new service"""
+ try:
+ form.ip_src = addr_parse(form.ip_src)
+ form.ip_dst = addr_parse(form.ip_dst)
+ except ValueError:
+ return {"status":"Invalid address"}
+
+ if ip_family(form.ip_dst) != ip_family(form.ip_src):
+ return {"status":"Destination and source addresses must be of the same family"}
+ if form.proto not in ["tcp", "udp"]:
+ return {"status":"Invalid protocol"}
+
+ srv_id = None
+ try:
+ srv_id = gen_service_id()
+ db.query("INSERT INTO services (service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ srv_id, False, form.public_port, form.proxy_port , form.name, form.proto, form.ip_src, form.ip_dst)
+ except sqlite3.IntegrityError:
+ return {'status': 'This type of service already exists'}
+
+ await firewall.reload()
+ await refresh_frontend()
+ return {'status': 'ok', 'service_id': srv_id}
diff --git a/backend/routers/regexproxy.py b/backend/routers/regexproxy.py
index dd49ec5..c7d565e 100644
--- a/backend/routers/regexproxy.py
+++ b/backend/routers/regexproxy.py
@@ -100,7 +100,7 @@ async def get_service_list():
""")
@app.get('/service/{service_id}', response_model=ServiceModel)
-async def get_service_by_id(service_id: str, ):
+async def get_service_by_id(service_id: str):
"""Get info about a specific service using his id"""
res = db.query("""
SELECT
@@ -118,28 +118,28 @@ async def get_service_by_id(service_id: str, ):
return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
-async def service_stop(service_id: str, ):
+async def service_stop(service_id: str):
"""Request the stop of a specific service"""
await firewall.get(service_id).next(STATUS.STOP)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/pause', response_model=StatusMessageModel)
-async def service_pause(service_id: str, ):
+async def service_pause(service_id: str):
"""Request the pause of a specific service"""
await firewall.get(service_id).next(STATUS.PAUSE)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
-async def service_start(service_id: str, ):
+async def service_start(service_id: str):
"""Request the start of a specific service"""
await firewall.get(service_id).next(STATUS.ACTIVE)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
-async def service_delete(service_id: str, ):
+async def service_delete(service_id: str):
"""Request the deletion of a specific service"""
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
@@ -149,7 +149,7 @@ async def service_delete(service_id: str, ):
@app.get('/service/{service_id}/regen-port', response_model=StatusMessageModel)
-async def regen_service_port(service_id: str, ):
+async def regen_service_port(service_id: str):
"""Request the regeneration of a the internal proxy port of a specific service"""
db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', gen_internal_port(db), service_id)
await firewall.get(service_id).update_port()
@@ -161,7 +161,7 @@ class ChangePortForm(BaseModel):
internalPort: Union[int, None]
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel)
-async def change_service_ports(service_id: str, change_port:ChangePortForm ):
+async def change_service_ports(service_id: str, change_port:ChangePortForm):
"""Choose and change the ports of the service"""
if change_port.port is None and change_port.internalPort is None:
return {'status': 'Invalid Request!'}
@@ -195,7 +195,7 @@ class RegexModel(BaseModel):
active:bool
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
-async def get_service_regexe_list(service_id: str, ):
+async def get_service_regexe_list(service_id: str):
"""Get the list of the regexes of a service"""
return db.query("""
SELECT
@@ -205,7 +205,7 @@ async def get_service_regexe_list(service_id: str, ):
""", service_id)
@app.get('/regex/{regex_id}', response_model=RegexModel)
-async def get_regex_by_id(regex_id: int, ):
+async def get_regex_by_id(regex_id: int):
"""Get regex info using his id"""
res = db.query("""
SELECT
@@ -217,7 +217,7 @@ async def get_regex_by_id(regex_id: int, ):
return res[0]
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
-async def regex_delete(regex_id: int, ):
+async def regex_delete(regex_id: int):
"""Delete a regex using his id"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
@@ -227,7 +227,7 @@ async def regex_delete(regex_id: int, ):
return {'status': 'ok'}
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
-async def regex_enable(regex_id: int, ):
+async def regex_enable(regex_id: int):
"""Request the enabling of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
@@ -237,7 +237,7 @@ async def regex_enable(regex_id: int, ):
return {'status': 'ok'}
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
-async def regex_disable(regex_id: int, ):
+async def regex_disable(regex_id: int):
"""Request the deactivation of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0:
@@ -255,7 +255,7 @@ class RegexAddForm(BaseModel):
is_case_sensitive: bool
@app.post('/regexes/add', response_model=StatusMessageModel)
-async def add_new_regex(form: RegexAddForm, ):
+async def add_new_regex(form: RegexAddForm):
"""Add a new regex"""
try:
re.compile(b64decode(form.regex))
@@ -283,7 +283,7 @@ class RenameForm(BaseModel):
name:str
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
-async def service_rename(service_id: str, form: RenameForm, ):
+async def service_rename(service_id: str, form: RenameForm):
"""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!'}
@@ -295,7 +295,7 @@ async def service_rename(service_id: str, form: RenameForm, ):
return {'status': 'ok'}
@app.post('/services/add', response_model=ServiceAddStatus)
-async def add_new_service(form: ServiceAddForm, ):
+async def add_new_service(form: ServiceAddForm):
"""Add a new service"""
serv_id = gen_service_id(db)
form.name = refactor_name(form.name)
diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py
index ed23156..052be3f 100755
--- a/backend/utils/__init__.py
+++ b/backend/utils/__init__.py
@@ -1,7 +1,6 @@
import asyncio
-from ipaddress import ip_interface
-import os, socket, psutil
-import sys
+from ipaddress import ip_address, ip_interface
+import os, socket, psutil, sys, nftables
from fastapi_socketio import SocketManager
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
@@ -38,6 +37,9 @@ def list_files(mypath):
def ip_parse(ip:str):
return str(ip_interface(ip).network)
+def addr_parse(ip:str):
+ return str(ip_address(ip))
+
def ip_family(ip:str):
return "ip6" if ip_interface(ip).version == 6 else "ip"
@@ -47,4 +49,60 @@ def get_interfaces():
for interf in interfs:
if interf.family in [socket.AF_INET, socket.AF_INET6]:
yield {"name": int_name, "addr":interf.address}
- return list(_get_interfaces())
\ No newline at end of file
+ return list(_get_interfaces())
+
+def nftables_int_to_json(ip_int):
+ ip_int = ip_parse(ip_int)
+ ip_addr = str(ip_int).split("/")[0]
+ ip_addr_cidr = int(str(ip_int).split("/")[1])
+ return {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}
+
+def nftables_json_to_int(ip_json_int):
+ if isinstance(ip_json_int,str):
+ return str(ip_parse(ip_json_int))
+ else:
+ return f'{ip_json_int["prefix"]["addr"]}/{ip_json_int["prefix"]["len"]}'
+
+class Singleton(object):
+ __instance = None
+ def __new__(class_, *args, **kwargs):
+ if not isinstance(class_.__instance, class_):
+ class_.__instance = object.__new__(class_, *args, **kwargs)
+ return class_.__instance
+
+class NFTableManager(Singleton):
+
+ table_name = "firegex"
+
+ def __init__(self, init_cmd, reset_cmd):
+ self.__init_cmds = init_cmd
+ self.__reset_cmds = reset_cmd
+ self.nft = nftables.Nftables()
+
+ def raw_cmd(self, *cmds):
+ return self.nft.json_cmd({"nftables": list(cmds)})
+
+ def cmd(self, *cmds):
+ code, out, err = self.raw_cmd(*cmds)
+
+ if code == 0: return out
+ else: raise Exception(err)
+
+ def init(self):
+ self.reset()
+ self.raw_cmd({"add":{"table":{"name":self.table_name,"family":"inet"}}})
+ self.cmd(*self.__init_cmds)
+
+ def reset(self):
+ self.raw_cmd(*self.__reset_cmds)
+
+ def list_rules(self, tables = None, chains = None):
+ for filter in [ele["rule"] for ele in self.raw_list() if "rule" in ele ]:
+ if tables and filter["table"] not in tables: continue
+ if chains and filter["chain"] not in chains: continue
+ yield filter
+
+ def raw_list(self):
+ return self.cmd({"list": {"ruleset": None}})["nftables"]
+
+
\ No newline at end of file
diff --git a/backend/utils/firegextables.py b/backend/utils/firegextables.py
deleted file mode 100644
index 8919625..0000000
--- a/backend/utils/firegextables.py
+++ /dev/null
@@ -1,125 +0,0 @@
-from typing import List
-import nftables
-from utils import ip_parse, ip_family
-
-class FiregexFilter():
- def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None):
- self.nftables = nftables.Nftables()
- self.id = int(id) if id else None
- self.queue = queue
- self.target = target
- self.proto = proto
- self.port = int(port)
- self.ip_int = str(ip_int)
-
- def __eq__(self, o: object) -> bool:
- if isinstance(o, FiregexFilter):
- return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
- return False
-
-class FiregexTables:
-
- def __init__(self):
- self.table_name = "firegex"
- self.nft = nftables.Nftables()
-
- def raw_cmd(self, *cmds):
- return self.nft.json_cmd({"nftables": list(cmds)})
-
- def cmd(self, *cmds):
- code, out, err = self.raw_cmd(*cmds)
-
- if code == 0: return out
- else: raise Exception(err)
-
- def init(self):
- self.reset()
- code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}})
- if code == 0:
- self.cmd(
- {"create":{"chain":{
- "family":"inet",
- "table":self.table_name,
- "name":"input",
- "type":"filter",
- "hook":"prerouting",
- "prio":-150,
- "policy":"accept"
- }}},
- {"create":{"chain":{
- "family":"inet",
- "table":self.table_name,
- "name":"output",
- "type":"filter",
- "hook":"postrouting",
- "prio":-150,
- "policy":"accept"
- }}}
- )
-
-
- def reset(self):
- self.raw_cmd(
- {"flush":{"table":{"name":"firegex","family":"inet"}}},
- {"delete":{"table":{"name":"firegex","family":"inet"}}},
- )
-
- def list(self):
- return self.cmd({"list": {"ruleset": None}})["nftables"]
-
- def add_output(self, queue_range, proto, port, ip_int):
- init, end = queue_range
- if init > end: init, end = end, init
- ip_int = ip_parse(ip_int)
- ip_addr = str(ip_int).split("/")[0]
- ip_addr_cidr = int(str(ip_int).split("/")[1])
- self.cmd({ "insert":{ "rule": {
- "family": "inet",
- "table": self.table_name,
- "chain": "output",
- "expr": [
- {'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'saddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}},
- {'match': {"left": { "payload": {"protocol": str(proto), "field": "sport"}}, "op": "==", "right": int(port)}},
- {"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
- ]
- }}})
-
- def add_input(self, queue_range, proto = None, port = None, ip_int = None):
- init, end = queue_range
- if init > end: init, end = end, init
- ip_int = ip_parse(ip_int)
- ip_addr = str(ip_int).split("/")[0]
- ip_addr_cidr = int(str(ip_int).split("/")[1])
- self.cmd({"insert":{"rule":{
- "family": "inet",
- "table": self.table_name,
- "chain": "input",
- "expr": [
- {'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'daddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}},
- {'match': {"left": { "payload": {"protocol": str(proto), "field": "dport"}}, "op": "==", "right": int(port)}},
- {"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
- ]
- }}})
-
- def get(self) -> List[FiregexFilter]:
- res = []
- for filter in [ele["rule"] for ele in self.list() if "rule" in ele and ele["rule"]["table"] == self.table_name]:
- queue_str = filter["expr"][2]["queue"]["num"]
- queue = None
- if isinstance(queue_str,dict): queue = int(queue_str["range"][0]), int(queue_str["range"][1])
- else: queue = int(queue_str), int(queue_str)
- ip_int = None
- if isinstance(filter["expr"][0]["match"]["right"],str):
- ip_int = str(ip_parse(filter["expr"][0]["match"]["right"]))
- else:
- ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}'
- res.append(FiregexFilter(
- target=filter["chain"],
- id=int(filter["handle"]),
- queue=queue,
- proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
- port=filter["expr"][1]["match"]["right"],
- ip_int=ip_int
- ))
- return res
-
\ No newline at end of file
diff --git a/backend/utils/loader.py b/backend/utils/loader.py
index f10e3b3..d60c217 100644
--- a/backend/utils/loader.py
+++ b/backend/utils/loader.py
@@ -90,7 +90,7 @@ def load_routers(app):
resets, startups, shutdowns = [], [], []
for router in get_router_modules():
if router.router:
- app.include_router(router.router, prefix=f"/{router.name}")
+ app.include_router(router.router, prefix=f"/{router.name}", tags=[router.name])
if router.reset:
resets.append(router.reset)
if router.startup:
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 5863c83..64ed9e8 100755
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -6,11 +6,12 @@ import { Outlet, Route, Routes } from 'react-router-dom';
import MainLayout from './components/MainLayout';
import { PasswordSend, ServerStatusResponse } from './js/models';
import { errorNotify, fireUpdateRequest, getstatus, HomeRedirector, login, setpassword } from './js/utils';
-import NFRegex from './pages/NFRegex.tsx';
+import NFRegex from './pages/NFRegex';
import io from 'socket.io-client';
import RegexProxy from './pages/RegexProxy';
-import ServiceDetailsNFRegex from './pages/NFRegex.tsx/ServiceDetails';
+import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
+import PortHijack from './pages/PortHijack';
const socket = io({transports: ["websocket", "polling"], path:"/sock" });
@@ -153,6 +154,7 @@ function App() {
} >
} />
+ } />
} />
diff --git a/frontend/src/_vars.scss b/frontend/src/_vars.scss
index e65237e..6df635d 100755
--- a/frontend/src/_vars.scss
+++ b/frontend/src/_vars.scss
@@ -1,4 +1,4 @@
$primary_color: #242a33;
-$second_color: #1A1B1E;
+$secondary_color: #1A1B1E;
$third_color:#25262b;
diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx
index 7f61c54..bcc4588 100755
--- a/frontend/src/components/Footer/index.tsx
+++ b/frontend/src/components/Footer/index.tsx
@@ -3,7 +3,6 @@ import React from 'react';
import style from "./index.module.scss";
-
function FooterPage() {
return