add disallowed packet types configuration for LetsMesh and implement filtering in storage collector

This commit is contained in:
Lloyd
2025-11-19 15:33:39 +00:00
parent 549696a6c8
commit f0ccf161c9
4 changed files with 141 additions and 36 deletions

View File

@@ -144,7 +144,24 @@ letsmesh:
enabled: true
iata_code: "test" # e.g., "SFO", "LHR", "test"
broker_index: 0 # Which LetsMesh broker (0=EU, 1=US West)
status_interval: 60
status_interval: 60
# Block specific packet types from being published to LetsMesh
# If not specified or empty list, all types are published
# Available types: REQ, RESPONSE, TXT_MSG, ACK, ADVERT, GRP_TXT,
# GRP_DATA, ANON_REQ, PATH, TRACE, RAW_CUSTOM
disallowed_packet_types: []
# - REQ # Don't publish requests
# - RESPONSE # Don't publish responses
# - TXT_MSG # Don't publish text messages
# - ACK # Don't publish acknowledgments
# - ADVERT # Don't publish advertisements
# - GRP_TXT # Don't publish group text messages
# - GRP_DATA # Don't publish group data
# - ANON_REQ # Don't publish anonymous requests
# - PATH # Don't publish path packets
# - TRACE # Don't publish trace packets
# - RAW_CUSTOM # Don't publish custom raw packets
logging:
# Log level: DEBUG, INFO, WARNING, ERROR

View File

@@ -29,13 +29,22 @@ def get_node_info(config: Dict[str, Any]) -> Dict[str, Any]:
letsmesh_config = config.get("letsmesh", {})
from pymc_core.protocol.utils import PAYLOAD_TYPES
disallowed_types = letsmesh_config.get("disallowed_packet_types", [])
type_name_map = {name: code for code, name in PAYLOAD_TYPES.items()}
disallowed_hex = [type_name_map.get(name.upper(), None) for name in disallowed_types]
disallowed_hex = [val for val in disallowed_hex if val is not None] # Filter out invalid names
return {
"node_name": node_name,
"radio_config": radio_config_str,
"iata_code": letsmesh_config.get("iata_code", "TEST"),
"broker_index": letsmesh_config.get("broker_index", 0),
"status_interval": letsmesh_config.get("status_interval", 60),
"model": letsmesh_config.get("model", "PyMC-Repeater")
"model": letsmesh_config.get("model", "PyMC-Repeater"),
"disallowed_packet_types": disallowed_hex
}

View File

@@ -5,6 +5,7 @@ import base64
import paho.mqtt.client as mqtt
from datetime import datetime, timedelta, UTC
from dataclasses import dataclass, asdict
from nacl.signing import SigningKey
from typing import Callable, Optional
from .. import __version__
@@ -301,3 +302,80 @@ class MeshCoreToMqttJwtPusher:
result = self.client.publish(topic, message, retain=retain)
logging.debug(f"Published to {topic}: {message}")
return result
# ====================================================================
# LetsMesh Packet Data Class
# ====================================================================
@dataclass
class LetsMeshPacket:
"""
Data class for LetsMesh packet format.
Converts internal packet_record format to LetsMesh publish format.
"""
origin: str
origin_id: str
timestamp: str
type: str
direction: str
time: str
date: str
len: str
packet_type: str
route: str
payload_len: str
raw: str
SNR: str
RSSI: str
score: str
duration: str
hash: str
@classmethod
def from_packet_record(cls, packet_record: dict, origin: str, origin_id: str) -> Optional['LetsMeshPacket']:
"""
Create LetsMeshPacket from internal packet_record format.
Args:
packet_record: Internal packet record dictionary
origin: Node name
origin_id: Public key of the node
Returns:
LetsMeshPacket instance or None if raw_packet is missing
"""
if "raw_packet" not in packet_record or not packet_record["raw_packet"]:
return None
# Extract timestamp and format date/time
timestamp = packet_record.get("timestamp", 0)
dt = datetime.fromtimestamp(timestamp)
# Format route type (1=Flood->F, 2=Direct->D, etc)
route_map = {1: "F", 2: "D"}
route = route_map.get(packet_record.get("route", 0), str(packet_record.get("route", 0)))
return cls(
origin=origin,
origin_id=origin_id,
timestamp=dt.isoformat(),
type="PACKET",
direction="rx",
time=dt.strftime("%H:%M:%S"),
date=dt.strftime("%-d/%-m/%Y"),
len=str(len(packet_record["raw_packet"]) // 2),
packet_type=str(packet_record.get("type", 0)),
route=route,
payload_len=str(packet_record.get("payload_length", 0)),
raw=packet_record["raw_packet"],
SNR=str(packet_record.get("snr", 0)),
RSSI=str(packet_record.get("rssi", 0)),
score=str(int(packet_record.get("score", 0) * 1000)),
duration="0",
hash=packet_record.get("packet_hash", "")
)
def to_dict(self) -> dict:
"""Convert to dictionary for JSON serialization"""
return asdict(self)

View File

@@ -8,7 +8,7 @@ from typing import Optional, Dict, Any
from .sqlite_handler import SQLiteHandler
from .rrdtool_handler import RRDToolHandler
from .mqtt_handler import MQTTHandler
from .letsmesh_handler import MeshCoreToMqttJwtPusher
from .letsmesh_handler import MeshCoreToMqttJwtPusher, LetsMeshPacket
logger = logging.getLogger("StorageCollector")
@@ -42,10 +42,23 @@ class StorageCollector:
stats_provider=self._get_live_stats
)
self.letsmesh_handler.connect()
# Get disallowed packet types from config
from ..config import get_node_info
node_info = get_node_info(config)
self.disallowed_packet_types = set(node_info["disallowed_packet_types"])
logger.info(f"LetsMesh handler initialized with public key: {public_key_hex[:16]}...")
if self.disallowed_packet_types:
logger.info(f"Disallowed packet types: {sorted(self.disallowed_packet_types)}")
else:
logger.info("All packet types allowed")
except Exception as e:
logger.error(f"Failed to initialize LetsMesh handler: {e}")
self.letsmesh_handler = None
self.disallowed_packet_types = set()
else:
self.disallowed_packet_types = set()
def _get_live_stats(self) -> dict:
"""Get live stats from RepeaterHandler"""
@@ -75,40 +88,28 @@ class StorageCollector:
# Publish to LetsMesh if enabled
if self.letsmesh_handler:
try:
# Format packet data for LetsMesh publish_packet
if "raw_packet" in packet_record and packet_record["raw_packet"]:
# Extract timestamp and format date/time
timestamp = packet_record.get("timestamp", time.time())
dt = datetime.fromtimestamp(timestamp)
# Check if packet has type field
if "type" not in packet_record:
logger.error("Cannot publish to LetsMesh: packet_record missing 'type' field")
return
packet_type = packet_record["type"]
if packet_type in self.disallowed_packet_types:
logger.debug(f"Skipped publishing packet type 0x{packet_type:02X} (in disallowed types)")
return
# Create LetsMesh packet from record
node_name = self.config.get("repeater", {}).get("node_name", "Unknown")
letsmesh_packet = LetsMeshPacket.from_packet_record(
packet_record,
origin=node_name,
origin_id=self.letsmesh_handler.public_key
)
if letsmesh_packet:
self.letsmesh_handler.publish_packet(letsmesh_packet.to_dict())
# Get node name from config
node_name = self.config.get("repeater", {}).get("node_name", "Unknown")
# Format route type (1=Flood->F, 2=Direct->D, etc)
route_map = {1: "F", 2: "D"}
route = route_map.get(packet_record.get("route", 0), str(packet_record.get("route", 0)))
letsmesh_packet = {
"origin": node_name,
"origin_id": self.letsmesh_handler.public_key,
"timestamp": dt.isoformat(),
"type": "PACKET",
"direction": "rx",
"time": dt.strftime("%H:%M:%S"),
"date": dt.strftime("%-d/%-m/%Y"),
"len": str(len(packet_record["raw_packet"]) // 2), # Raw packet length in bytes
"packet_type": str(packet_record.get("type", 0)),
"route": route,
"payload_len": str(packet_record.get("payload_length", 0)),
"raw": packet_record["raw_packet"],
"SNR": str(packet_record.get("snr", 0)),
"RSSI": str(packet_record.get("rssi", 0)),
"score": str(int(packet_record.get("score", 0) * 1000)), # Convert to integer score
"duration": "0", # Not available in our packet record
"hash": packet_record.get("packet_hash", "")
}
self.letsmesh_handler.publish_packet(letsmesh_packet)
except Exception as e:
logger.error(f"Failed to publish packet to LetsMesh: {e}")