6 Commits

3 changed files with 165 additions and 3 deletions

View File

@@ -5,8 +5,12 @@ from __future__ import annotations
import logging as lg
import os
import platform
import subprocess
import uuid
from typing import Any
import psutil
from judas_protocol import Message
from judas_client.connector import Connector
@@ -41,12 +45,17 @@ class Client:
self.logger.debug(f"ID: {self.id}")
self.connector: Connector = Connector(
client=self,
mac_address=self.id,
host=self.server_host,
port=self.server_port,
on_message=self.handle_message,
)
self.initial_telemetry: dict[str, Any] = (
self._gather_initial_telemetry()
)
def _get_mac_address(self) -> str:
"""Get the MAC address of the client.
@@ -61,6 +70,144 @@ class Client:
)
return mac_address
def _get_cpu_model(self) -> str:
"""Get the CPU model name.
Returns:
str: The CPU model name, or "Unknown" if it cannot be determined.
"""
system = platform.system()
if system == "Windows":
try:
import winreg
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"HARDWARE\DESCRIPTION\System\CentralProcessor\0",
)
model, _ = winreg.QueryValueEx(key, "ProcessorNameString")
winreg.CloseKey(key)
return model.strip()
except Exception:
return platform.processor()
elif system == "Linux":
try:
with open("/proc/cpuinfo", "r") as f:
for line in f:
if "model name" in line:
return line.split(":")[1].strip()
except Exception:
return platform.processor()
elif system == "Darwin":
try:
return (
subprocess.check_output(
["sysctl", "-n", "machdep.cpu.brand_string"]
)
.decode()
.strip()
)
except Exception:
return platform.processor()
return platform.processor()
def _gather_initial_telemetry(self) -> dict[str, Any]:
"""Gather initial telemetry data."""
self.logger.debug("Gathering initial telemetry data...")
inventory: dict[str, Any] = {}
# platform info
inventory["platform"] = {
"system": platform.system(),
"node": platform.node(),
"release": platform.release(),
"version": platform.version(),
"architecture": platform.architecture()[0],
}
# CPU information
cpu_frequency = psutil.cpu_freq()
inventory["cpu"] = {
"model": self._get_cpu_model(),
"physical_cores": psutil.cpu_count(logical=False),
"threads": psutil.cpu_count(logical=True),
"max_frequency_mhz": round(cpu_frequency.max, 2)
if cpu_frequency
else "Unknown",
"min_frequency_mhz": round(cpu_frequency.min, 2)
if cpu_frequency
else "Unknown",
}
# Memory information
virtual_memory = psutil.virtual_memory()
swap_memory = psutil.swap_memory()
inventory["memory"] = {
"total_ram": round(virtual_memory.total / (1024**2), 2),
"total_swap": round(swap_memory.total / (1024**2), 2),
}
# Disk information
disk_partitions = psutil.disk_partitions()
inventory["disks"] = []
for partition in disk_partitions:
try:
usage = psutil.disk_usage(partition.mountpoint)
inventory["disks"].append(
{
"device": partition.device,
"mountpoint": partition.mountpoint,
"filesystem": partition.fstype,
"total_size_gb": round(usage.total / (1024**3), 2),
}
)
except (PermissionError, OSError) as e:
self.logger.warning(
f"Permission denied for disk partition: {partition.device}\n{e}"
)
continue
# Network information
network_interfaces = psutil.net_if_addrs()
inventory["network"] = []
for interface_name, addresses in network_interfaces.items():
interface_data = {
"interface": interface_name,
"ip_addresses": [],
"mac_address": None,
}
for address in addresses:
if address.family.name in ["AF_LINK", "AF_PACKET"]:
interface_data["mac_address"] = address.address
elif address.family.name == "AF_INET":
interface_data["ip_addresses"].append(address.address)
inventory["network"].append(interface_data)
# User information
inventory["users"] = []
for user in psutil.users():
inventory["users"].append(
{
"name": user.name,
"terminal": user.terminal,
"host": user.host,
"started": user.started,
}
)
# Python environment information
inventory["python"] = {
"version": platform.python_version(),
"implementation": platform.python_implementation(),
}
return inventory
def handle_message(self, message: Message) -> None:
"""Handle incoming messages.

View File

@@ -5,16 +5,20 @@ import logging as lg
import selectors
import socket
import time
from typing import Callable
from typing import Callable, TYPE_CHECKING
from judas_protocol import Category, ControlAction, Message
if TYPE_CHECKING:
from judas_client.client import Client
class Connector:
"""Connector class for managing TCP connection and message exchange."""
def __init__(
self,
client: Client,
mac_address: str,
host: str,
port: int,
@@ -34,6 +38,8 @@ class Connector:
)
self.logger.debug("Initializing Connector...")
self.client: Client = client
self.host: str = host
self.port: int = port
@@ -110,6 +116,14 @@ class Connector:
hello_message: Message = Message.Control.hello(self.mac_address)
self.send(hello_message)
def send_initial_telemetry(self) -> None:
"""Send initial telemetry data to the server."""
self.logger.debug("[*] Sending initial telemetry...")
telemetry_message: Message = Message.Telemetry.initial(
self.client.initial_telemetry
)
self.send(telemetry_message)
def close(self) -> None:
"""Close the connection and clean up resources."""
self.logger.debug("[*] Closing connection...")
@@ -171,6 +185,7 @@ class Connector:
self.logger.debug("[*] Connected, sending HELLO...")
self.send_hello()
self.send_initial_telemetry()
def run(self) -> None:
"""Run the main event loop."""

4
uv.lock generated
View File

@@ -322,8 +322,8 @@ test = [
[[package]]
name = "judas-protocol"
version = "0.9.0"
source = { git = "https://gitea.pufereq.pl/judas/judas_protocol.git#3d5a1e95daa4cd99b51abdcaca9967fa8f921ec1" }
version = "0.9.1"
source = { git = "https://gitea.pufereq.pl/judas/judas_protocol.git#085c34f232f95313d66db48a7d17bc25c92a35ae" }
[[package]]
name = "markdown-it-py"