diff --git a/rrcd/cli.py b/rrcd/cli.py index a6c5945..6d39a47 100644 --- a/rrcd/cli.py +++ b/rrcd/cli.py @@ -151,8 +151,15 @@ include_joined_member_list = false nick_max_chars = 32 # Limits. +# These limits help mitigate abuse and resource exhaustion, but can be adjusted +# based on your use case. +# +# N.B. max_msg_body_bytes should not allow messages so large that they cannot +# fit within the link MTU after UTF-8 encoding and envelope overhead. The +# default of 350 bytes is a safe choice for the default Reticulum MTU of 500. max_rooms_per_session = 32 max_room_name_len = 64 +max_msg_body_bytes = 350 rate_limit_msgs_per_minute = 240 # Hub-initiated liveness checks (0 disables). @@ -332,6 +339,12 @@ def _build_arg_parser() -> argparse.ArgumentParser: default=None, help="Per-link message rate limit", ) + p.add_argument( + "--max-msg-body-bytes", + type=int, + default=None, + help="Maximum message body size in UTF-8 bytes", + ) p.add_argument( "--ping-interval", @@ -420,6 +433,8 @@ def main(argv: list[str] | None = None) -> None: cfg = replace( cfg, rate_limit_msgs_per_minute=int(args.rate_limit_msgs_per_minute) ) + if args.max_msg_body_bytes is not None: + cfg = replace(cfg, max_msg_body_bytes=int(args.max_msg_body_bytes)) if args.ping_interval is not None: cfg = replace(cfg, ping_interval_s=float(args.ping_interval)) diff --git a/rrcd/config.py b/rrcd/config.py index 2c62a11..8c9429c 100644 --- a/rrcd/config.py +++ b/rrcd/config.py @@ -28,6 +28,7 @@ class HubRuntimeConfig: nick_max_chars: int = 32 max_rooms_per_session: int = 32 max_room_name_len: int = 64 + max_msg_body_bytes: int = 350 rate_limit_msgs_per_minute: int = 240 ping_interval_s: float = 0.0 ping_timeout_s: float = 0.0 diff --git a/rrcd/router.py b/rrcd/router.py index 9e7429a..8b6d135 100644 --- a/rrcd/router.py +++ b/rrcd/router.py @@ -686,6 +686,26 @@ class MessageRouter: text="message requires room name", ) return + + # Validate message body size (UTF-8 bytes) + if isinstance(body, str): + body_bytes = len(body.encode('utf-8', errors='replace')) + if body_bytes > self.hub.config.max_msg_body_bytes: + if self.hub.identity is not None: + self.hub.message_helper.emit_error( + outgoing, + link, + src=self.hub.identity.hash, + text=f"message too large: {body_bytes} bytes > {self.hub.config.max_msg_body_bytes} bytes", + ) + self.log.info( + "Rejected oversized message peer=%s nick=%r body_bytes=%s limit=%s", + self.hub._fmt_hash(peer_hash), + sess.get("nick"), + body_bytes, + self.hub.config.max_msg_body_bytes, + ) + return elif t == T_NOTICE: if not isinstance(room, str) or not room: return