nfproxy module writing: written part of the firegex lib, frontend refactored and improved, c++ improves
This commit is contained in:
7
fgex-lib/firegex/__init__.py
Normal file
7
fgex-lib/firegex/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
__version__ = "{{VERSION_PLACEHOLDER}}" if "{" not in "{{VERSION_PLACEHOLDER}}" else "0.0.0"
|
||||
|
||||
#Exported functions
|
||||
__all__ = [
|
||||
|
||||
]
|
||||
6
fgex-lib/firegex/__main__.py
Normal file
6
fgex-lib/firegex/__main__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from firegex.cli import run
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
5
fgex-lib/firegex/cli.py
Normal file
5
fgex-lib/firegex/cli.py
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
def run():
|
||||
pass # TODO implement me
|
||||
|
||||
38
fgex-lib/firegex/nfproxy/__init__.py
Normal file
38
fgex-lib/firegex/nfproxy/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import functools
|
||||
|
||||
ACCEPT = 0
|
||||
DROP = 1
|
||||
REJECT = 2
|
||||
MANGLE = 3
|
||||
EXCEPTION = 4
|
||||
INVALID = 5
|
||||
|
||||
def pyfilter(func):
|
||||
"""
|
||||
Decorator to mark functions that will be used in the proxy.
|
||||
Stores the function reference in a global registry.
|
||||
"""
|
||||
if not hasattr(pyfilter, "registry"):
|
||||
pyfilter.registry = set()
|
||||
|
||||
pyfilter.registry.add(func.__name__)
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
def get_pyfilters():
|
||||
"""Returns the list of functions marked with @pyfilter."""
|
||||
return list(pyfilter.registry)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
161
fgex-lib/firegex/nfproxy/internals.py
Normal file
161
fgex-lib/firegex/nfproxy/internals.py
Normal file
@@ -0,0 +1,161 @@
|
||||
from inspect import signature
|
||||
from firegex.nfproxy.params import RawPacket, NotReadyToRun
|
||||
from firegex.nfproxy import ACCEPT, DROP, REJECT, MANGLE, EXCEPTION, INVALID
|
||||
|
||||
RESULTS = [
|
||||
ACCEPT,
|
||||
DROP,
|
||||
REJECT,
|
||||
MANGLE,
|
||||
EXCEPTION,
|
||||
INVALID
|
||||
]
|
||||
FULL_STREAM_ACTIONS = [
|
||||
"flush"
|
||||
"accept",
|
||||
"reject",
|
||||
"drop"
|
||||
]
|
||||
|
||||
type_annotations_associations = {
|
||||
"tcp": {
|
||||
RawPacket: RawPacket.fetch_from_global
|
||||
},
|
||||
"http": {
|
||||
RawPacket: RawPacket.fetch_from_global
|
||||
}
|
||||
}
|
||||
|
||||
def _generate_filter_structure(filters: list[str], proto:str, glob:dict, local:dict):
|
||||
if proto not in type_annotations_associations.keys():
|
||||
raise Exception("Invalid protocol")
|
||||
|
||||
res = []
|
||||
|
||||
valid_annotation_type = type_annotations_associations[proto]
|
||||
def add_func_to_list(func):
|
||||
if not callable(func):
|
||||
raise Exception(f"{func} is not a function")
|
||||
sig = signature(func)
|
||||
params_function = []
|
||||
|
||||
for k, v in sig.parameters.items():
|
||||
if v.annotation in valid_annotation_type.keys():
|
||||
params_function.append((v.annotation, valid_annotation_type[v.annotation]))
|
||||
else:
|
||||
raise Exception(f"Invalid type annotation {v.annotation} for function {func.__name__}")
|
||||
res.append((func, params_function))
|
||||
|
||||
for filter in filters:
|
||||
if not isinstance(filter, str):
|
||||
raise Exception("Invalid filter list: must be a list of strings")
|
||||
if filter in glob.keys():
|
||||
add_func_to_list(glob[filter])
|
||||
elif filter in local.keys():
|
||||
add_func_to_list(local[filter])
|
||||
else:
|
||||
raise Exception(f"Filter {filter} not found")
|
||||
|
||||
return res
|
||||
|
||||
def get_filters_info(code:str, proto:str):
|
||||
glob = {}
|
||||
local = {}
|
||||
exec(code, glob, local)
|
||||
exec("import firegex.nfproxy", glob, local)
|
||||
filters = eval("firegex.nfproxy.get_pyfilters()", glob, local)
|
||||
return _generate_filter_structure(filters, proto, glob, local)
|
||||
|
||||
def get_filter_names(code:str, proto:str):
|
||||
return [ele[0].__name__ for ele in get_filters_info(code, proto)]
|
||||
|
||||
def compile():
|
||||
glob = globals()
|
||||
local = locals()
|
||||
filters = glob["__firegex_pyfilter_enabled"]
|
||||
proto = glob["__firegex_proto"]
|
||||
glob["__firegex_func_list"] = _generate_filter_structure(filters, proto, glob, local)
|
||||
glob["__firegex_stream"] = []
|
||||
glob["__firegex_stream_size"] = 0
|
||||
|
||||
if "FGEX_STREAM_MAX_SIZE" in local and int(local["FGEX_STREAM_MAX_SIZE"]) > 0:
|
||||
glob["__firegex_stream_max_size"] = int(local["FGEX_STREAM_MAX_SIZE"])
|
||||
elif "FGEX_STREAM_MAX_SIZE" in glob and int(glob["FGEX_STREAM_MAX_SIZE"]) > 0:
|
||||
glob["__firegex_stream_max_size"] = int(glob["FGEX_STREAM_MAX_SIZE"])
|
||||
else:
|
||||
glob["__firegex_stream_max_size"] = 1*8e20 # 1MB default value
|
||||
|
||||
if "FGEX_FULL_STREAM_ACTION" in local and local["FGEX_FULL_STREAM_ACTION"] in FULL_STREAM_ACTIONS:
|
||||
glob["__firegex_full_stream_action"] = local["FGEX_FULL_STREAM_ACTION"]
|
||||
else:
|
||||
glob["__firegex_full_stream_action"] = "flush"
|
||||
|
||||
glob["__firegex_pyfilter_result"] = None
|
||||
|
||||
def handle_packet():
|
||||
glob = globals()
|
||||
func_list = glob["__firegex_func_list"]
|
||||
final_result = ACCEPT
|
||||
cache_call = {}
|
||||
cache_call[RawPacket] = RawPacket.fetch_from_global()
|
||||
data_size = len(cache_call[RawPacket].data)
|
||||
if glob["__firegex_stream_size"]+data_size > glob["__firegex_stream_max_size"]:
|
||||
match glob["__firegex_full_stream_action"]:
|
||||
case "flush":
|
||||
glob["__firegex_stream"] = []
|
||||
glob["__firegex_stream_size"] = 0
|
||||
case "accept":
|
||||
glob["__firegex_pyfilter_result"] = {
|
||||
"action": ACCEPT,
|
||||
"matched_by": None,
|
||||
"mangled_packet": None
|
||||
}
|
||||
return
|
||||
case "reject":
|
||||
glob["__firegex_pyfilter_result"] = {
|
||||
"action": REJECT,
|
||||
"matched_by": "@MAX_STREAM_SIZE_REACHED",
|
||||
"mangled_packet": None
|
||||
}
|
||||
return
|
||||
case "drop":
|
||||
glob["__firegex_pyfilter_result"] = {
|
||||
"action": DROP,
|
||||
"matched_by": "@MAX_STREAM_SIZE_REACHED",
|
||||
"mangled_packet": None
|
||||
}
|
||||
return
|
||||
glob["__firegex_stream"].append(cache_call[RawPacket])
|
||||
glob["__firegex_stream_size"] += data_size
|
||||
func_name = None
|
||||
mangled_packet = None
|
||||
for filter in func_list:
|
||||
final_params = []
|
||||
for ele in filter[1]:
|
||||
if ele[0] not in cache_call.keys():
|
||||
try:
|
||||
cache_call[ele[0]] = ele[1]()
|
||||
except NotReadyToRun:
|
||||
cache_call[ele[0]] = None
|
||||
if cache_call[ele[0]] is None:
|
||||
continue # Parsing raised NotReadyToRun, skip filter
|
||||
final_params.append(cache_call[ele[0]])
|
||||
res = filter[0](*final_params)
|
||||
if res is None:
|
||||
continue #ACCEPTED
|
||||
if res == MANGLE:
|
||||
if RawPacket not in cache_call.keys():
|
||||
continue #Packet not modified
|
||||
pkt:RawPacket = cache_call[RawPacket]
|
||||
mangled_packet = pkt.raw_packet
|
||||
break
|
||||
elif res != ACCEPT:
|
||||
final_result = res
|
||||
func_name = filter[0].__name__
|
||||
break
|
||||
glob["__firegex_pyfilter_result"] = {
|
||||
"action": final_result,
|
||||
"matched_by": func_name,
|
||||
"mangled_packet": mangled_packet
|
||||
}
|
||||
|
||||
71
fgex-lib/firegex/nfproxy/params.py
Normal file
71
fgex-lib/firegex/nfproxy/params.py
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
class NotReadyToRun(Exception): # raise this exception if the stream state is not ready to parse this object, the call will be skipped
|
||||
pass
|
||||
|
||||
class RawPacket:
|
||||
def __init__(self,
|
||||
data: bytes,
|
||||
raw_packet: bytes,
|
||||
is_input: bool,
|
||||
is_ipv6: bool,
|
||||
is_tcp: bool,
|
||||
):
|
||||
self.__data = bytes(data)
|
||||
self.__raw_packet = bytes(raw_packet)
|
||||
self.__is_input = bool(is_input)
|
||||
self.__is_ipv6 = bool(is_ipv6)
|
||||
self.__is_tcp = bool(is_tcp)
|
||||
|
||||
@property
|
||||
def is_input(self) -> bool:
|
||||
return self.__is_input
|
||||
|
||||
@property
|
||||
def is_ipv6(self) -> bool:
|
||||
return self.__is_ipv6
|
||||
|
||||
@property
|
||||
def is_tcp(self) -> bool:
|
||||
return self.__is_tcp
|
||||
|
||||
@property
|
||||
def data(self) -> bytes:
|
||||
return self.__data
|
||||
|
||||
@property
|
||||
def proto_header(self) -> bytes:
|
||||
return self.__raw_packet[:self.proto_header_len]
|
||||
|
||||
@property
|
||||
def proto_header_len(self) -> int:
|
||||
return len(self.__raw_packet) - len(self.__data)
|
||||
|
||||
@data.setter
|
||||
def data(self, v:bytes):
|
||||
if not isinstance(v, bytes):
|
||||
raise Exception("Invalid data type, data MUST be of type bytes")
|
||||
self.__raw_packet = self.proto_header + v
|
||||
self.__data = v
|
||||
|
||||
@property
|
||||
def raw_packet(self) -> bytes:
|
||||
return self.__raw_packet
|
||||
|
||||
@raw_packet.setter
|
||||
def raw_packet(self, v:bytes):
|
||||
if not isinstance(v, bytes):
|
||||
raise Exception("Invalid data type, data MUST be of type bytes")
|
||||
if len(v) < self.proto_header_len:
|
||||
raise Exception("Invalid packet length")
|
||||
header_len = self.proto_header_len
|
||||
self.__data = v[header_len:]
|
||||
self.__raw_packet = v
|
||||
|
||||
@staticmethod
|
||||
def fetch_from_global():
|
||||
glob = globals()
|
||||
if "__firegex_packet_info" not in glob.keys():
|
||||
raise Exception("Packet info not found")
|
||||
return RawPacket(**glob["__firegex_packet_info"])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user