Changing from 'Global' Flood to 'Unscoped' flood as '*' doesn't actually mean wildcard, it means unscoped. Region keys should still only be forwaded if they're whitelisted. UI changes pending

This commit is contained in:
Joshua Mesilane
2026-04-06 22:32:05 +10:00
parent 3010703e1b
commit 38e1fbe3f9
3 changed files with 43 additions and 41 deletions

View File

@@ -152,12 +152,12 @@ def save_config(config_data: Dict[str, Any], config_path: Optional[str] = None)
return False
def update_global_flood_policy(allow: bool, config_path: Optional[str] = None) -> bool:
def update_unscoped_flood_policy(allow: bool, config_path: Optional[str] = None) -> bool:
"""
Update the global flood policy in the configuration.
Update the unscoped flood policy in the configuration.
Args:
allow: True to allow flooding globally, False to deny
allow: True to allow unscoped flooding, False to deny
config_path: Path to config file (uses default if None)
Returns:
@@ -173,12 +173,13 @@ def update_global_flood_policy(allow: bool, config_path: Optional[str] = None) -
# Set global flood policy
config["mesh"]["global_flood_allow"] = allow
config["mesh"]["unscoped_flood_allow"] = allow
# Save updated config
return save_config(config, config_path)
except Exception as e:
logger.error(f"Failed to update global flood policy: {e}")
logger.error(f"Failed to update unscoped flood policy: {e}")
return False

View File

@@ -623,10 +623,10 @@ class RepeaterHandler(BaseHandler):
route_type = packet.header & PH_ROUTE_MASK
if route_type == ROUTE_TYPE_FLOOD:
# Check if global flood policy blocked it
global_flood_allow = self.config.get("mesh", {}).get("global_flood_allow", True)
if not global_flood_allow:
return "Global flood policy disabled"
# Check if unscoped flood policy blocked it
unscoped_flood_allow = self.config.get("mesh", {}).get("unscoped_flood_allow", self.config.get("mesh", {}).get("global_flood_allow", True))
if not unscoped_flood_allow:
return "Unscoped flood policy disabled"
if route_type == ROUTE_TYPE_DIRECT:
hash_size = packet.get_path_hash_size()
@@ -800,19 +800,20 @@ class RepeaterHandler(BaseHandler):
if not packet.drop_reason:
packet.drop_reason = "Marked do not retransmit"
return None
# Check unscoped flood policy
unscoped_flood_allow = self.config.get("mesh", {}).get("unscoped_flood_allow", self.config.get("mesh", {}).get("global_flood_allow", True))
route_type = packet.header & PH_ROUTE_MASK
if route_type == ROUTE_TYPE_FLOOD:
if not unscoped_flood_allow:
packet.drop_reason = "Unscoped flood policy disabled"
return None
# Check global flood policy
global_flood_allow = self.config.get("mesh", {}).get("global_flood_allow", True)
if not global_flood_allow:
route_type = packet.header & PH_ROUTE_MASK
if route_type == ROUTE_TYPE_FLOOD or route_type == ROUTE_TYPE_TRANSPORT_FLOOD:
allowed, check_reason = self._check_transport_codes(packet)
if not allowed:
packet.drop_reason = check_reason
return None
else:
packet.drop_reason = "Global flood policy disabled"
#Check transport scopes flood policy
if route_type == ROUTE_TYPE_TRANSPORT_FLOOD:
allowed, check_reason = self._check_transport_codes(packet)
if not allowed:
packet.drop_reason = "Transport code not allowed to flood"
return None
mode = self._get_loop_detect_mode()
@@ -1134,7 +1135,7 @@ class RepeaterHandler(BaseHandler):
"web": self.config.get("web", {}), # Include web configuration
"mesh": {
"loop_detect": self.config.get("mesh", {}).get("loop_detect", "off"),
"global_flood_allow": self.config.get("mesh", {}).get("global_flood_allow", True),
"unscoped_flood_allow": self.config.get("mesh", {}).get("unscoped_flood_allow", self.config.get("mesh", {}).get("global_flood_allow", True)),
"path_hash_mode": self.config.get("mesh", {}).get("path_hash_mode", 0),
},
"letsmesh": self.config.get("letsmesh", {}),

View File

@@ -15,7 +15,7 @@ from repeater.companion.identity_resolve import (
find_companion_index,
heal_companion_empty_names,
)
from repeater.config import update_global_flood_policy
from repeater.config import update_unscoped_flood_policy
from .auth.middleware import require_auth
from .auth_endpoints import AuthAPIEndpoints
@@ -93,8 +93,8 @@ logger = logging.getLogger("HTTPServer")
# DELETE /api/transport_key?key_id=X - Delete transport key
# Network Policy
# GET /api/global_flood_policy - Get global flood policy
# POST /api/global_flood_policy - Update global flood policy
# GET /api/unscoped_flood_policy - Get unscoped flood policy
# POST /api/unscoped_flood_policy - Update unscoped flood policy
# POST /api/ping_neighbor - Ping a neighbor node
# Identity Management
@@ -2153,57 +2153,57 @@ class APIEndpoints:
@cherrypy.expose
@cherrypy.tools.json_out()
@cherrypy.tools.json_in()
def global_flood_policy(self):
def unscoped_flood_policy(self):
"""
Update global flood policy configuration
Update unscoped flood policy configuration
POST /global_flood_policy
Body: {"global_flood_allow": true/false}
POST /unscoped_flood_policy
Body: {"unscoped_flood_allow": true/false}
"""
if cherrypy.request.method == "POST":
try:
data = cherrypy.request.json or {}
global_flood_allow = data.get("global_flood_allow")
unscoped_flood_allow = data.get("unscoped_flood_allow")
if global_flood_allow is None:
return self._error("Missing required field: global_flood_allow")
if unscoped_flood_allow is None:
return self._error("Missing required field: unscoped_flood_allow")
if not isinstance(global_flood_allow, bool):
return self._error("global_flood_allow must be a boolean value")
if not isinstance(unscoped_flood_allow, bool):
return self._error("unscoped_flood_allow must be a boolean value")
# Update the running configuration first (like CAD settings)
if "mesh" not in self.config:
self.config["mesh"] = {}
self.config["mesh"]["global_flood_allow"] = global_flood_allow
self.config["mesh"]["unscoped_flood_allow"] = unscoped_flood_allow
# Get the actual config path from daemon instance (same as CAD settings)
config_path = getattr(self, "_config_path", "/etc/pymc_repeater/config.yaml")
if self.daemon_instance and hasattr(self.daemon_instance, "config_path"):
config_path = self.daemon_instance.config_path
logger.info(f"Using config path for global flood policy: {config_path}")
logger.info(f"Using config path for unscoped flood policy: {config_path}")
# Update the configuration file using ConfigManager
try:
saved = self.config_manager.save_to_file()
if saved:
logger.info(
f"Updated running config and saved global flood policy to file: {'allow' if global_flood_allow else 'deny'}"
f"Updated running config and saved unscoped flood policy to file: {'allow' if unscoped_flood_allow else 'deny'}"
)
else:
logger.error("Failed to save global flood policy to file")
logger.error("Failed to save unscoped flood policy to file")
return self._error("Failed to save configuration to file")
except Exception as e:
logger.error(f"Failed to save global flood policy to file: {e}")
logger.error(f"Failed to save unscoped flood policy to file: {e}")
return self._error(f"Failed to save configuration to file: {e}")
return self._success(
{"global_flood_allow": global_flood_allow},
message=f"Global flood policy updated to {'allow' if global_flood_allow else 'deny'} (live and saved)",
{"unscoped_flood_allow": unscoped_flood_allow},
message=f"Unscoped flood policy updated to {'allow' if unscoped_flood_allow else 'deny'} (live and saved)",
)
except Exception as e:
logger.error(f"Error updating global flood policy: {e}")
logger.error(f"Error updating unscoped flood policy: {e}")
return self._error(e)
else:
return self._error("Method not supported")