From 38e1fbe3f992f59e122e573d51bb54cfc45b652f Mon Sep 17 00:00:00 2001 From: Joshua Mesilane Date: Mon, 6 Apr 2026 22:32:05 +1000 Subject: [PATCH] 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 --- repeater/config.py | 9 ++++---- repeater/engine.py | 35 +++++++++++++++--------------- repeater/web/api_endpoints.py | 40 +++++++++++++++++------------------ 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/repeater/config.py b/repeater/config.py index 6ba716b..604a85f 100644 --- a/repeater/config.py +++ b/repeater/config.py @@ -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 diff --git a/repeater/engine.py b/repeater/engine.py index febcbaa..533c63f 100644 --- a/repeater/engine.py +++ b/repeater/engine.py @@ -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", {}), diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index 6a58c31..750109e 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -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")