move rule

This commit is contained in:
2026-02-28 15:29:50 +01:00
parent 2c6cb2b7d9
commit 60fd926fc6
4 changed files with 467 additions and 141 deletions

View File

@@ -131,7 +131,7 @@ class NftManager:
if isinstance(expr, list):
tokens: List[str] = []
for part in expr:
if "match" in part:
if isinstance(part, dict) and "match" in part:
m = part["match"]
left = m.get("left")
right = m.get("right")
@@ -143,18 +143,18 @@ class NftManager:
tokens.append(f"{prot} {field} {right}")
continue
tokens.append("match")
elif "payload" in part:
elif isinstance(part, dict) and "payload" in part:
p = part["payload"]
prot = p.get("protocol")
field = p.get("field")
tokens.append(f"payload({prot}.{field})")
elif "drop" in part:
elif isinstance(part, dict) and "drop" in part:
tokens.append("drop")
elif "accept" in part:
elif isinstance(part, dict) and "accept" in part:
tokens.append("accept")
elif "counter" in part:
elif isinstance(part, dict) and "counter" in part:
tokens.append("counter")
elif "queue" in part:
elif isinstance(part, dict) and "queue" in part:
# handle fallback queue textualization
q = part["queue"]
if isinstance(q, dict):
@@ -172,7 +172,11 @@ class NftManager:
else:
tokens.append(f"queue num {q}")
else:
tokens.append("+".join(part.keys()))
# fallback for unknown dict token
if isinstance(part, dict):
tokens.append("+".join(part.keys()))
else:
tokens.append(str(part))
rule_lines.append(" ".join(tokens))
else:
rule_lines.append(json.dumps(r))
@@ -283,11 +287,13 @@ class MoveRequest(BaseModel):
to_top: Optional[bool] = Field(False, description="Move to top (equivalent to position=0)")
to_bottom: Optional[bool] = Field(False, description="Move to bottom (append)")
class MoveSubResult(BaseModel):
cmd: str
out: Optional[ExecResult] = None
err: Optional[str] = None
class MoveResult(BaseModel):
added: MoveSubResult
deleted: MoveSubResult
@@ -325,22 +331,22 @@ def parse_priority(val: Any) -> Optional[int]:
# numeric string
if isinstance(val, str):
s = val.strip()
if s.isdigit() or (s.startswith("-") and s[1:].isdigit()):
# try integer parse
try:
return int(s)
except Exception:
try:
return int(s)
# sometimes it's "0.0" or similar
return int(float(s))
except Exception:
return None
# sometimes nft uses "0" etc with whitespace
try:
return int(float(s))
except Exception:
return None
# nested dicts sometimes appear
if isinstance(val, dict):
# look for common keys
for key in ("priority", "prio"):
if key in val:
return parse_priority(val.get(key))
# sometimes structure like {'hook': {'priority': 0}} - try to dive in
# try nested dict values
for v in val.values():
p = parse_priority(v)
if p is not None:
@@ -594,6 +600,7 @@ def expr_to_text(expr: Any) -> Optional[str]:
elif isinstance(q, (int, float)):
token += f" num {int(q)}"
elif isinstance(q, str):
# preserve string but ensure spacing: client must supply numeric if nft expects it
token += f" num {q}"
parts.append(token)
continue
@@ -855,15 +862,19 @@ def move_rule(handle: int, req: MoveRequest):
chain_rules = ch.get("rules", [])
break
if not chain_rules:
if chain_rules is None or len(chain_rules) == 0:
raise HTTPException(status_code=404, detail="Chain not found or chain contains no rules")
# find the rule by handle
source_rule = None
for r in chain_rules:
if r.get("handle") == handle:
source_rule = r
break
# handle may be int or convertible; do tolerant compare
try:
if r.get("handle") is not None and int(r.get("handle")) == int(handle):
source_rule = r
break
except Exception:
continue
if source_rule is None:
raise HTTPException(status_code=404, detail=f"Rule with handle {handle} not found in chain")
@@ -881,7 +892,7 @@ def move_rule(handle: int, req: MoveRequest):
target_pos = len(chain_rules) # append
elif req.before_handle is not None:
# find index of before_handle
idx = next((i for i, rr in enumerate(chain_rules) if rr.get("handle") == req.before_handle), None)
idx = next((i for i, rr in enumerate(chain_rules) if rr.get("handle") is not None and int(rr.get("handle")) == int(req.before_handle)), None)
if idx is None:
raise HTTPException(status_code=404, detail=f"before_handle {req.before_handle} not found in chain")
target_pos = idx
@@ -932,20 +943,21 @@ def move_rule(handle: int, req: MoveRequest):
# Now delete the original rule by handle
try:
mgr.delete_rule_by_handle_text(family=family, table=table, chain=chain, handle=handle)
mgr.delete_rule_by_handle_text(family=family, table=table, chain=chain, handle=int(handle))
del_exec = MoveSubResult(cmd=f"delete rule {family} {table} {chain} handle {handle}", out=ExecResult(rc=0, stdout="", stderr=""), err=None)
except Exception as e:
# We succeeded adding but failed deleting - report both
err_msg = f"added new rule but deleting original handle {handle} failed: {e}"
logger.exception(err_msg)
del_exec = MoveSubResult(cmd=f"delete rule {family} {table} {chain} handle {handle}", out=None, err=str(e))
# Return 500 to indicate partial failure
return MoveResult(added=add_exec, deleted=del_exec)
# Return a 500 (partial success)
raise HTTPException(status_code=500, detail={"added": add_exec, "deleted": del_exec})
# success
return MoveResult(added=add_exec, deleted=del_exec)
except HTTPException:
# re-raise so FastAPI handles it
raise
except Exception as e:
logger.exception("move_rule internal error")