push: code changes
This commit is contained in:
@@ -1,11 +1,23 @@
|
||||
import functools
|
||||
from firegex.nfproxy.params import RawPacket
|
||||
from enum import Enum
|
||||
|
||||
ACCEPT = 0
|
||||
DROP = 1
|
||||
REJECT = 2
|
||||
MANGLE = 3
|
||||
EXCEPTION = 4
|
||||
INVALID = 5
|
||||
class Action(Enum):
|
||||
ACCEPT = 0
|
||||
DROP = 1
|
||||
REJECT = 2
|
||||
MANGLE = 3
|
||||
|
||||
class FullStreamAction(Enum):
|
||||
FLUSH = 0
|
||||
ACCEPT = 1
|
||||
REJECT = 2
|
||||
DROP = 3
|
||||
|
||||
ACCEPT = Action.ACCEPT
|
||||
DROP = Action.DROP
|
||||
REJECT = Action.REJECT
|
||||
MANGLE = Action.MANGLE
|
||||
|
||||
def pyfilter(func):
|
||||
"""
|
||||
@@ -27,12 +39,14 @@ def get_pyfilters():
|
||||
"""Returns the list of functions marked with @pyfilter."""
|
||||
return list(pyfilter.registry)
|
||||
|
||||
def clear_pyfilter_registry():
|
||||
"""Clears the pyfilter registry."""
|
||||
if hasattr(pyfilter, "registry"):
|
||||
pyfilter.registry.clear()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ACCEPT", "DROP", "REJECT", "MANGLE", "EXCEPTION", "INVALID",
|
||||
"Action", "FullStreamAction",
|
||||
"pyfilter",
|
||||
"RawPacket"
|
||||
]
|
||||
@@ -1,21 +1,7 @@
|
||||
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"
|
||||
]
|
||||
from firegex.nfproxy import Action, FullStreamAction
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
type_annotations_associations = {
|
||||
"tcp": {
|
||||
@@ -26,136 +12,178 @@ type_annotations_associations = {
|
||||
}
|
||||
}
|
||||
|
||||
def _generate_filter_structure(filters: list[str], proto:str, glob:dict, local:dict):
|
||||
@dataclass
|
||||
class FilterHandler:
|
||||
func: callable
|
||||
name: str
|
||||
params: dict[type, callable]
|
||||
proto: str
|
||||
|
||||
class internal_data:
|
||||
filter_call_info: list[FilterHandler] = []
|
||||
stream: list[RawPacket] = []
|
||||
stream_size: int = 0
|
||||
stream_max_size: int = 1*8e20
|
||||
full_stream_action: str = "flush"
|
||||
filter_glob: dict = {}
|
||||
|
||||
@dataclass
|
||||
class PacketHandlerResult:
|
||||
glob: dict = field(repr=False)
|
||||
action: Action = Action.ACCEPT
|
||||
matched_by: str = None
|
||||
mangled_packet: bytes = None
|
||||
|
||||
def set_result(self) -> None:
|
||||
self.glob["__firegex_pyfilter_result"] = {
|
||||
"action": self.action.value,
|
||||
"matched_by": self.matched_by,
|
||||
"mangled_packet": self.mangled_packet
|
||||
}
|
||||
|
||||
def reset_result(self) -> None:
|
||||
self.glob["__firegex_pyfilter_result"] = None
|
||||
|
||||
def context_call(func, *args, **kargs):
|
||||
internal_data.filter_glob["__firegex_tmp_args"] = args
|
||||
internal_data.filter_glob["__firegex_tmp_kargs"] = kargs
|
||||
internal_data.filter_glob["__firege_tmp_call"] = func
|
||||
res = eval("__firege_tmp_call(*__firegex_tmp_args, **__firegex_tmp_kargs)", internal_data.filter_glob, internal_data.filter_glob)
|
||||
del internal_data.filter_glob["__firegex_tmp_args"]
|
||||
del internal_data.filter_glob["__firegex_tmp_kargs"]
|
||||
del internal_data.filter_glob["__firege_tmp_call"]
|
||||
return res
|
||||
|
||||
def generate_filter_structure(filters: list[str], proto:str, glob:dict) -> list[FilterHandler]:
|
||||
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 = []
|
||||
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]))
|
||||
params_function[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))
|
||||
|
||||
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])
|
||||
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):
|
||||
def get_filters_info(code:str, proto:str) -> list[FilterHandler]:
|
||||
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)
|
||||
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):
|
||||
return [ele[0].__name__ for ele in get_filters_info(code, proto)]
|
||||
def get_filter_names(code:str, proto:str) -> list[str]:
|
||||
return [ele.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
|
||||
def handle_packet() -> None:
|
||||
cache_call = {} # Cache of the data handler calls
|
||||
|
||||
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
|
||||
pkt_info = RawPacket.fetch_from_global(internal_data.filter_glob)
|
||||
cache_call[RawPacket] = pkt_info
|
||||
|
||||
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"
|
||||
final_result = Action.ACCEPT
|
||||
data_size = len(pkt_info.data)
|
||||
|
||||
result = PacketHandlerResult(internal_data.filter_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
|
||||
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
|
||||
|
||||
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:
|
||||
for filter in internal_data.filter_call_info:
|
||||
final_params = []
|
||||
for ele in filter[1]:
|
||||
if ele[0] not in cache_call.keys():
|
||||
for data_type, data_func in filter.params.items():
|
||||
if data_type not in cache_call.keys():
|
||||
try:
|
||||
cache_call[ele[0]] = ele[1]()
|
||||
cache_call[data_type] = data_func(internal_data.filter_glob)
|
||||
except NotReadyToRun:
|
||||
cache_call[ele[0]] = None
|
||||
if cache_call[ele[0]] is None:
|
||||
cache_call[data_type] = None
|
||||
if cache_call[data_type] is None:
|
||||
continue # Parsing raised NotReadyToRun, skip filter
|
||||
final_params.append(cache_call[ele[0]])
|
||||
res = filter[0](*final_params)
|
||||
final_params.append(cache_call[data_type])
|
||||
|
||||
res = context_call(filter.func, *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:
|
||||
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
|
||||
func_name = filter[0].__name__
|
||||
break
|
||||
glob["__firegex_pyfilter_result"] = {
|
||||
"action": final_result,
|
||||
"matched_by": func_name,
|
||||
"mangled_packet": mangled_packet
|
||||
}
|
||||
|
||||
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.filter_glob = glob
|
||||
|
||||
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()
|
||||
|
||||
@@ -9,12 +9,15 @@ class RawPacket:
|
||||
is_input: bool,
|
||||
is_ipv6: bool,
|
||||
is_tcp: bool,
|
||||
l4_size: int,
|
||||
):
|
||||
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)
|
||||
self.__l4_size = int(l4_size)
|
||||
self.__raw_packet_header_size = len(self.__raw_packet)-self.__l4_size
|
||||
|
||||
@property
|
||||
def is_input(self) -> bool:
|
||||
@@ -33,19 +36,25 @@ class RawPacket:
|
||||
return self.__data
|
||||
|
||||
@property
|
||||
def proto_header(self) -> bytes:
|
||||
return self.__raw_packet[:self.proto_header_len]
|
||||
def l4_size(self) -> int:
|
||||
return self.__l4_size
|
||||
|
||||
@property
|
||||
def proto_header_len(self) -> int:
|
||||
return len(self.__raw_packet) - len(self.__data)
|
||||
def raw_packet_header_len(self) -> int:
|
||||
return self.__raw_packet_header_size
|
||||
|
||||
@data.setter
|
||||
def data(self, v:bytes):
|
||||
@property
|
||||
def l4_data(self) -> bytes:
|
||||
return self.__raw_packet[self.raw_packet_header_len:]
|
||||
|
||||
@l4_data.setter
|
||||
def l4_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
|
||||
#if len(v) != self.__l4_size:
|
||||
# raise Exception("Invalid data size, must be equal to the original packet header size (due to a technical limitation)")
|
||||
self.__raw_packet = self.__raw_packet[:self.raw_packet_header_len]+v
|
||||
self.__l4_size = len(v)
|
||||
|
||||
@property
|
||||
def raw_packet(self) -> bytes:
|
||||
@@ -55,17 +64,16 @@ class RawPacket:
|
||||
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:]
|
||||
#if len(v) != len(self.__raw_packet):
|
||||
# raise Exception("Invalid data size, must be equal to the original packet size (due to a technical limitation)")
|
||||
if len(v) < self.raw_packet_header_len:
|
||||
raise Exception("Invalid data size, must be greater than the original packet header size")
|
||||
self.__raw_packet = v
|
||||
self.__l4_size = len(v)-self.raw_packet_header_len
|
||||
|
||||
@staticmethod
|
||||
def fetch_from_global():
|
||||
glob = globals()
|
||||
@classmethod
|
||||
def fetch_from_global(cls, glob):
|
||||
if "__firegex_packet_info" not in glob.keys():
|
||||
raise Exception("Packet info not found")
|
||||
return RawPacket(**glob["__firegex_packet_info"])
|
||||
|
||||
return cls(**glob["__firegex_packet_info"])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user