fix nfqueue
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
This commit is contained in:
@@ -158,15 +158,19 @@ class NftManager:
|
|||||||
# handle fallback queue textualization
|
# handle fallback queue textualization
|
||||||
q = part["queue"]
|
q = part["queue"]
|
||||||
if isinstance(q, dict):
|
if isinstance(q, dict):
|
||||||
num = q.get("num") or q.get("number") or q.get("range") or q.get("from")
|
num = q.get("num") or q.get("number") or q.get("queue_number") or q.get("from") or q.get("range")
|
||||||
tok = "queue"
|
tok = "queue"
|
||||||
if num is not None:
|
if num is not None:
|
||||||
tok += f" num{num}"
|
tok += f" num {num}"
|
||||||
if q.get("bypass"):
|
if q.get("bypass"):
|
||||||
tok += " bypass"
|
tok += " bypass"
|
||||||
tokens.append(tok)
|
tokens.append(tok)
|
||||||
else:
|
else:
|
||||||
tokens.append(f"queue{q}")
|
# numeric or string value
|
||||||
|
if isinstance(q, (int, float)):
|
||||||
|
tokens.append(f"queue num {int(q)}")
|
||||||
|
else:
|
||||||
|
tokens.append(f"queue num {q}")
|
||||||
else:
|
else:
|
||||||
tokens.append("+".join(part.keys()))
|
tokens.append("+".join(part.keys()))
|
||||||
rule_lines.append(" ".join(tokens))
|
rule_lines.append(" ".join(tokens))
|
||||||
@@ -253,13 +257,10 @@ class CreateRuleRequest(BaseModel):
|
|||||||
class Config:
|
class Config:
|
||||||
schema_extra = {
|
schema_extra = {
|
||||||
"example": {
|
"example": {
|
||||||
"family": "inet",
|
"family": "bridge",
|
||||||
"table": "filter",
|
"table": "filter",
|
||||||
"chain": "input",
|
"chain": "forward",
|
||||||
"expr": [
|
"expr": [{"match": {"left": {"payload": {"protocol": "ip", "field": "protocol"}}, "op": "==", "right": "icmp"}}, {"drop": None}],
|
||||||
{"match": {"left": {"payload": {"protocol": "ip", "field": "protocol"}}, "op": "==", "right": "icmp"}},
|
|
||||||
{"queue": {"num": 0, "bypass": True}}
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,23 +332,6 @@ def rule_text_from_expr(expr: Any) -> str:
|
|||||||
tokens: List[str] = []
|
tokens: List[str] = []
|
||||||
for part in expr:
|
for part in expr:
|
||||||
if isinstance(part, dict):
|
if isinstance(part, dict):
|
||||||
# queue handling: support {'queue': 0}, {'queue': '0-3'}, {'queue': {'num': 0, 'bypass': True}}
|
|
||||||
if "queue" in part:
|
|
||||||
q = part["queue"]
|
|
||||||
token = "queue"
|
|
||||||
if isinstance(q, dict):
|
|
||||||
num = q.get("num") or q.get("number") or q.get("queue_number") or q.get("from") or q.get("range")
|
|
||||||
if num is not None:
|
|
||||||
token += f" num{num}"
|
|
||||||
if q.get("bypass"):
|
|
||||||
token += " bypass"
|
|
||||||
elif isinstance(q, (int, float)):
|
|
||||||
token += f" num{int(q)}"
|
|
||||||
elif isinstance(q, str):
|
|
||||||
token += f" num{q}"
|
|
||||||
tokens.append(token)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# common tokens
|
# common tokens
|
||||||
if "match" in part:
|
if "match" in part:
|
||||||
m = part["match"]
|
m = part["match"]
|
||||||
@@ -380,6 +364,20 @@ def rule_text_from_expr(expr: Any) -> str:
|
|||||||
elif "tcp" in part or "udp" in part:
|
elif "tcp" in part or "udp" in part:
|
||||||
proto = "tcp" if "tcp" in part else "udp"
|
proto = "tcp" if "tcp" in part else "udp"
|
||||||
tokens.append(proto)
|
tokens.append(proto)
|
||||||
|
elif "queue" in part:
|
||||||
|
q = part["queue"]
|
||||||
|
token = "queue"
|
||||||
|
if isinstance(q, dict):
|
||||||
|
num = q.get("num") or q.get("number") or q.get("queue_number") or q.get("from") or q.get("range")
|
||||||
|
if num is not None:
|
||||||
|
token += f" num {num}"
|
||||||
|
if q.get("bypass"):
|
||||||
|
token += " bypass"
|
||||||
|
elif isinstance(q, (int, float)):
|
||||||
|
token += f" num {int(q)}"
|
||||||
|
elif isinstance(q, str):
|
||||||
|
token += f" num {q}"
|
||||||
|
tokens.append(token)
|
||||||
else:
|
else:
|
||||||
keys = "+".join(sorted(part.keys()))
|
keys = "+".join(sorted(part.keys()))
|
||||||
tokens.append(keys)
|
tokens.append(keys)
|
||||||
@@ -427,11 +425,11 @@ def build_predictable_ruleset(nft_json: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
# extract metadata robustly
|
# extract metadata robustly
|
||||||
ch_type = ch.get("type")
|
ch_type = ch.get("type")
|
||||||
ch_hook = ch.get("hook")
|
ch_hook = ch.get("hook")
|
||||||
# priority may be provided in several forms; try them
|
# try multiple keys for priority/prio shapes
|
||||||
ch_priority = parse_priority(ch.get("priority") if "priority" in ch else ch.get("prio") if "prio" in ch else ch.get("priority", None))
|
ch_priority = parse_priority(ch.get("priority") if "priority" in ch else ch.get("prio") if "prio" in ch else ch.get("priority", None))
|
||||||
# further attempt if ch_hook is dict
|
# also attempt to parse nested shapes if present (some nft JSON variations)
|
||||||
if ch_priority is None and isinstance(ch.get("hook"), dict):
|
if ch_priority is None:
|
||||||
ch_priority = parse_priority(ch.get("hook").get("priority") if ch.get("hook") else None)
|
ch_priority = parse_priority(ch.get("hook") if isinstance(ch.get("hook"), dict) else None)
|
||||||
|
|
||||||
ch_policy = ch.get("policy")
|
ch_policy = ch.get("policy")
|
||||||
|
|
||||||
@@ -482,12 +480,16 @@ def build_predictable_ruleset(nft_json: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
chains_map[chain_name]["rules"].append(rule_obj)
|
chains_map[chain_name]["rules"].append(rule_obj)
|
||||||
|
|
||||||
# Attempt to salvage chain metadata from rule record if present
|
# Attempt to salvage chain metadata from rule record if present
|
||||||
|
# some nft JSON may include 'chain' subfields inside rule record
|
||||||
|
# e.g. r.get('chain') might be an object - handle that defensively
|
||||||
if isinstance(r.get("chain"), dict):
|
if isinstance(r.get("chain"), dict):
|
||||||
csub = r.get("chain")
|
csub = r.get("chain")
|
||||||
|
# try to parse nested priority
|
||||||
if chains_map[chain_name].get("priority") is None:
|
if chains_map[chain_name].get("priority") is None:
|
||||||
parsed_prio = parse_priority(csub.get("priority") if "priority" in csub else csub.get("prio"))
|
parsed_prio = parse_priority(csub.get("priority") if "priority" in csub else csub.get("prio"))
|
||||||
if parsed_prio is not None:
|
if parsed_prio is not None:
|
||||||
chains_map[chain_name]["priority"] = parsed_prio
|
chains_map[chain_name]["priority"] = parsed_prio
|
||||||
|
# type/hook/policy from nested if present
|
||||||
if chains_map[chain_name].get("type") is None and csub.get("type") is not None:
|
if chains_map[chain_name].get("type") is None and csub.get("type") is not None:
|
||||||
chains_map[chain_name]["type"] = csub.get("type")
|
chains_map[chain_name]["type"] = csub.get("type")
|
||||||
if chains_map[chain_name].get("hook") is None and csub.get("hook") is not None:
|
if chains_map[chain_name].get("hook") is None and csub.get("hook") is not None:
|
||||||
@@ -522,10 +524,12 @@ def expr_to_text(expr: Any) -> Optional[str]:
|
|||||||
Best-effort renderer that converts a typical nft JSON expr (list) into a textual
|
Best-effort renderer that converts a typical nft JSON expr (list) into a textual
|
||||||
fragment suitable to append to 'add rule <family> <table> <chain> ...'.
|
fragment suitable to append to 'add rule <family> <table> <chain> ...'.
|
||||||
Returns None when it cannot deterministically render the provided expr.
|
Returns None when it cannot deterministically render the provided expr.
|
||||||
Supports queue + bypass:
|
Supported cases (common):
|
||||||
{'queue': 0} -> "queue num0"
|
- [{'match': {'left': {'payload': {'protocol':'ip','field':'protocol'}}, 'op':'==', 'right':'icmp'}}, {'drop': None}]
|
||||||
{'queue': '0-3'} -> "queue num0-3"
|
-> 'ip protocol icmp drop'
|
||||||
{'queue': {'num': 0, 'bypass': True}} -> "queue num0 bypass"
|
- payload / tcp / udp / counter / accept
|
||||||
|
- queue tokens and optional bypass support
|
||||||
|
This intentionally does not attempt to support every nft JSON construct.
|
||||||
"""
|
"""
|
||||||
if expr is None:
|
if expr is None:
|
||||||
return ""
|
return ""
|
||||||
@@ -549,20 +553,20 @@ def expr_to_text(expr: Any) -> Optional[str]:
|
|||||||
parts.append("counter")
|
parts.append("counter")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# queue support (NEW)
|
# queue support: allow {"queue": 1} or {"queue": {"num":1, "bypass": True}} etc.
|
||||||
if "queue" in element:
|
if "queue" in element:
|
||||||
q = element["queue"]
|
q = element["queue"]
|
||||||
token = "queue"
|
token = "queue"
|
||||||
if isinstance(q, dict):
|
if isinstance(q, dict):
|
||||||
num = q.get("num") or q.get("number") or q.get("queue_number") or q.get("from") or q.get("range")
|
num = q.get("num") or q.get("number") or q.get("queue_number") or q.get("from") or q.get("range")
|
||||||
if num is not None:
|
if num is not None:
|
||||||
token += f" num{num}"
|
token += f" num {num}"
|
||||||
if q.get("bypass"):
|
if q.get("bypass"):
|
||||||
token += " bypass"
|
token += " bypass"
|
||||||
elif isinstance(q, (int, float)):
|
elif isinstance(q, (int, float)):
|
||||||
token += f" num{int(q)}"
|
token += f" num {int(q)}"
|
||||||
elif isinstance(q, str):
|
elif isinstance(q, str):
|
||||||
token += f" num{q}"
|
token += f" num {q}"
|
||||||
parts.append(token)
|
parts.append(token)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -578,9 +582,11 @@ def expr_to_text(expr: Any) -> Optional[str]:
|
|||||||
field = p.get("field")
|
field = p.get("field")
|
||||||
# common: protocol field match (protocol == icmp)
|
# common: protocol field match (protocol == icmp)
|
||||||
if prot and field and isinstance(right, str):
|
if prot and field and isinstance(right, str):
|
||||||
|
# ip vs ip6 decision is left to the frontend; here we render 'ip protocol icmp' (works for many setups)
|
||||||
if field == "protocol":
|
if field == "protocol":
|
||||||
parts.append(f"{prot} {field} {right}")
|
parts.append(f"{prot} {field} {right}")
|
||||||
continue
|
continue
|
||||||
|
# payload might be l4 ports etc; produce generic payload(...) token
|
||||||
parts.append(f"payload({prot}.{field}) {right}")
|
parts.append(f"payload({prot}.{field}) {right}")
|
||||||
continue
|
continue
|
||||||
# fallback for match: try to stringify right
|
# fallback for match: try to stringify right
|
||||||
@@ -731,8 +737,11 @@ def create_rule_json(req: CreateRuleRequest):
|
|||||||
logger.warning("create_rule_json: failed to list chain for verification: %s", e_chain)
|
logger.warning("create_rule_json: failed to list chain for verification: %s", e_chain)
|
||||||
|
|
||||||
# If we reach here -> treat as error: return 400 with exec_res in body.
|
# If we reach here -> treat as error: return 400 with exec_res in body.
|
||||||
|
# FastAPI cannot both raise HTTPException and include ExecResult as body easily, so raise HTTPException
|
||||||
|
# with detail that includes stderr and the executed cmd.
|
||||||
detail = f"nft command failed rc={rc}. stderr: {stderr!r}. cmd: {cmd}"
|
detail = f"nft command failed rc={rc}. stderr: {stderr!r}. cmd: {cmd}"
|
||||||
logger.warning("create_rule_json failed: %s", detail)
|
logger.warning("create_rule_json failed: %s", detail)
|
||||||
|
# Return an HTTPException with the detail (frontend can still inspect error.response.data if ExecResult was included)
|
||||||
raise HTTPException(status_code=400, detail=detail)
|
raise HTTPException(status_code=400, detail=detail)
|
||||||
|
|
||||||
except NftError as e:
|
except NftError as e:
|
||||||
@@ -746,6 +755,7 @@ def create_rule_json(req: CreateRuleRequest):
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/rules/{handle}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete rule by handle")
|
@router.delete("/rules/{handle}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete rule by handle")
|
||||||
def delete_rule(handle: int, family: str = "inet", table: str = "filter", chain: str = "input"):
|
def delete_rule(handle: int, family: str = "inet", table: str = "filter", chain: str = "input"):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user