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
+18 -1
View File
@@ -144,7 +144,24 @@ letsmesh:
enabled: true enabled: true
iata_code: "test" # e.g., "SFO", "LHR", "test" iata_code: "test" # e.g., "SFO", "LHR", "test"
broker_index: 0 # Which LetsMesh broker (0=EU, 1=US West) 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: logging:
# Log level: DEBUG, INFO, WARNING, ERROR # Log level: DEBUG, INFO, WARNING, ERROR
+10 -1
View File
@@ -29,13 +29,22 @@ def get_node_info(config: Dict[str, Any]) -> Dict[str, Any]:
letsmesh_config = config.get("letsmesh", {}) 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 { return {
"node_name": node_name, "node_name": node_name,
"radio_config": radio_config_str, "radio_config": radio_config_str,
"iata_code": letsmesh_config.get("iata_code", "TEST"), "iata_code": letsmesh_config.get("iata_code", "TEST"),
"broker_index": letsmesh_config.get("broker_index", 0), "broker_index": letsmesh_config.get("broker_index", 0),
"status_interval": letsmesh_config.get("status_interval", 60), "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
} }
@@ -5,6 +5,7 @@ import base64
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from datetime import datetime, timedelta, UTC from datetime import datetime, timedelta, UTC
from dataclasses import dataclass, asdict
from nacl.signing import SigningKey from nacl.signing import SigningKey
from typing import Callable, Optional from typing import Callable, Optional
from .. import __version__ from .. import __version__
@@ -301,3 +302,80 @@ class MeshCoreToMqttJwtPusher:
result = self.client.publish(topic, message, retain=retain) result = self.client.publish(topic, message, retain=retain)
logging.debug(f"Published to {topic}: {message}") logging.debug(f"Published to {topic}: {message}")
return result 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)
+35 -34
View File
@@ -8,7 +8,7 @@ from typing import Optional, Dict, Any
from .sqlite_handler import SQLiteHandler from .sqlite_handler import SQLiteHandler
from .rrdtool_handler import RRDToolHandler from .rrdtool_handler import RRDToolHandler
from .mqtt_handler import MQTTHandler from .mqtt_handler import MQTTHandler
from .letsmesh_handler import MeshCoreToMqttJwtPusher from .letsmesh_handler import MeshCoreToMqttJwtPusher, LetsMeshPacket
logger = logging.getLogger("StorageCollector") logger = logging.getLogger("StorageCollector")
@@ -42,10 +42,23 @@ class StorageCollector:
stats_provider=self._get_live_stats stats_provider=self._get_live_stats
) )
self.letsmesh_handler.connect() 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]}...") 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: except Exception as e:
logger.error(f"Failed to initialize LetsMesh handler: {e}") logger.error(f"Failed to initialize LetsMesh handler: {e}")
self.letsmesh_handler = None self.letsmesh_handler = None
self.disallowed_packet_types = set()
else:
self.disallowed_packet_types = set()
def _get_live_stats(self) -> dict: def _get_live_stats(self) -> dict:
"""Get live stats from RepeaterHandler""" """Get live stats from RepeaterHandler"""
@@ -75,40 +88,28 @@ class StorageCollector:
# Publish to LetsMesh if enabled # Publish to LetsMesh if enabled
if self.letsmesh_handler: if self.letsmesh_handler:
try: try:
# Format packet data for LetsMesh publish_packet # Check if packet has type field
if "raw_packet" in packet_record and packet_record["raw_packet"]: if "type" not in packet_record:
# Extract timestamp and format date/time logger.error("Cannot publish to LetsMesh: packet_record missing 'type' field")
timestamp = packet_record.get("timestamp", time.time()) return
dt = datetime.fromtimestamp(timestamp)
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: except Exception as e:
logger.error(f"Failed to publish packet to LetsMesh: {e}") logger.error(f"Failed to publish packet to LetsMesh: {e}")