163 lines
5.9 KiB
Python
163 lines
5.9 KiB
Python
from inspect import signature
|
|
from firegex.nfproxy.internals.models import Action, FullStreamAction
|
|
from firegex.nfproxy.internals.models import FilterHandler, PacketHandlerResult
|
|
import functools
|
|
from firegex.nfproxy.internals.data import DataStreamCtx
|
|
from firegex.nfproxy.internals.exceptions import NotReadyToRun
|
|
from firegex.nfproxy.internals.data import RawPacket
|
|
|
|
def context_call(glob, func, *args, **kargs):
|
|
glob["__firegex_tmp_args"] = args
|
|
glob["__firegex_tmp_kargs"] = kargs
|
|
glob["__firege_tmp_call"] = func
|
|
res = eval("__firege_tmp_call(*__firegex_tmp_args, **__firegex_tmp_kargs)", glob, glob)
|
|
del glob["__firegex_tmp_args"]
|
|
del glob["__firegex_tmp_kargs"]
|
|
del glob["__firege_tmp_call"]
|
|
return res
|
|
|
|
def generate_filter_structure(filters: list[str], proto:str, glob:dict) -> list[FilterHandler]:
|
|
from firegex.nfproxy.models import type_annotations_associations
|
|
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[v.annotation] = valid_annotation_type[v.annotation]
|
|
else:
|
|
raise Exception(f"Invalid type annotation {v.annotation} for function {func.__name__}")
|
|
|
|
res.append(
|
|
FilterHandler(
|
|
func=func,
|
|
name=func.__name__,
|
|
params=params_function,
|
|
proto=proto
|
|
)
|
|
)
|
|
|
|
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])
|
|
else:
|
|
raise Exception(f"Filter {filter} not found")
|
|
return res
|
|
|
|
def get_filters_info(code:str, proto:str) -> list[FilterHandler]:
|
|
glob = {}
|
|
exec(code, glob, glob)
|
|
exec("import firegex.nfproxy", glob, glob)
|
|
filters = eval("firegex.nfproxy.get_pyfilters()", glob, glob)
|
|
try:
|
|
return generate_filter_structure(filters, proto, glob)
|
|
finally:
|
|
exec("firegex.nfproxy.clear_pyfilter_registry()", glob, glob)
|
|
|
|
|
|
def get_filter_names(code:str, proto:str) -> list[str]:
|
|
return [ele.name for ele in get_filters_info(code, proto)]
|
|
|
|
def handle_packet(glob: dict) -> None:
|
|
internal_data = DataStreamCtx(glob)
|
|
print("I'm here", flush=True)
|
|
cache_call = {} # Cache of the data handler calls
|
|
|
|
pkt_info = RawPacket._fetch_packet(internal_data)
|
|
internal_data.current_pkt = pkt_info
|
|
cache_call[RawPacket] = pkt_info
|
|
|
|
final_result = Action.ACCEPT
|
|
data_size = len(pkt_info.data)
|
|
|
|
result = PacketHandlerResult(glob)
|
|
|
|
if internal_data.stream_size+data_size > internal_data.stream_max_size:
|
|
match internal_data.full_stream_action:
|
|
case FullStreamAction.FLUSH:
|
|
internal_data.stream = []
|
|
internal_data.stream_size = 0
|
|
for func in internal_data.flush_action_set:
|
|
func()
|
|
case FullStreamAction.ACCEPT:
|
|
result.action = Action.ACCEPT
|
|
return result.set_result()
|
|
case FullStreamAction.REJECT:
|
|
result.action = Action.REJECT
|
|
result.matched_by = "@MAX_STREAM_SIZE_REACHED"
|
|
return result.set_result()
|
|
case FullStreamAction.REJECT:
|
|
result.action = Action.DROP
|
|
result.matched_by = "@MAX_STREAM_SIZE_REACHED"
|
|
return result.set_result()
|
|
|
|
internal_data.stream.append(pkt_info)
|
|
internal_data.stream_size += data_size
|
|
|
|
func_name = None
|
|
mangled_packet = None
|
|
for filter in internal_data.filter_call_info:
|
|
final_params = []
|
|
skip_call = False
|
|
for data_type, data_func in filter.params.items():
|
|
if data_type not in cache_call.keys():
|
|
try:
|
|
cache_call[data_type] = data_func(internal_data)
|
|
except NotReadyToRun:
|
|
cache_call[data_type] = None
|
|
skip_call = True
|
|
break
|
|
final_params.append(cache_call[data_type])
|
|
if skip_call:
|
|
continue
|
|
res = context_call(glob, filter.func, *final_params)
|
|
|
|
if res is None:
|
|
continue #ACCEPTED
|
|
if not isinstance(res, Action):
|
|
raise Exception(f"Invalid return type {type(res)} for function {filter.name}")
|
|
if res == Action.MANGLE:
|
|
mangled_packet = pkt_info.raw_packet
|
|
if res != Action.ACCEPT:
|
|
func_name = filter.name
|
|
final_result = res
|
|
break
|
|
|
|
result.action = final_result
|
|
result.matched_by = func_name
|
|
result.mangled_packet = mangled_packet
|
|
|
|
return result.set_result()
|
|
|
|
|
|
def compile(glob:dict) -> None:
|
|
internal_data = DataStreamCtx(glob)
|
|
|
|
glob["print"] = functools.partial(print, flush = True)
|
|
|
|
filters = glob["__firegex_pyfilter_enabled"]
|
|
proto = glob["__firegex_proto"]
|
|
|
|
internal_data.filter_call_info = generate_filter_structure(filters, proto, glob)
|
|
|
|
if "FGEX_STREAM_MAX_SIZE" in glob and int(glob["FGEX_STREAM_MAX_SIZE"]) > 0:
|
|
internal_data.stream_max_size = int(glob["FGEX_STREAM_MAX_SIZE"])
|
|
else:
|
|
internal_data.stream_max_size = 1*8e20 # 1MB default value
|
|
|
|
if "FGEX_FULL_STREAM_ACTION" in glob and isinstance(glob["FGEX_FULL_STREAM_ACTION"], FullStreamAction):
|
|
internal_data.full_stream_action = glob["FGEX_FULL_STREAM_ACTION"]
|
|
else:
|
|
internal_data.full_stream_action = FullStreamAction.FLUSH
|
|
|
|
PacketHandlerResult(glob).reset_result()
|
|
|