Converted `NetworkConnection` schema/response.

This commit is contained in:
Fabio Manganiello 2023-04-22 11:17:54 +02:00
parent d473b5d836
commit b3a0896485
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
7 changed files with 173 additions and 69 deletions

View File

@ -28,39 +28,6 @@ class SensorResponse(SystemResponse):
pass
class NetworkConnectionResponse(NetworkResponse):
# noinspection PyShadowingBuiltins
def __init__(
self,
fd: int,
family: str,
type: str,
local_address: str,
local_port: int,
remote_address: str,
remote_port: int,
status: str,
pid: int,
*args,
**kwargs
):
super().__init__(
*args,
output={
'fd': fd,
'family': family,
'type': type,
'local_address': local_address,
'local_port': local_port,
'remote_address': remote_address,
'remote_port': remote_port,
'status': status,
'pid': pid,
},
**kwargs
)
class NetworkAddressResponse(NetworkResponse):
def __init__(
self,

View File

@ -23,7 +23,6 @@ from platypush.entities.system import (
)
from platypush.message.response.system import (
NetworkResponseList,
NetworkConnectionResponse,
NetworkAddressResponse,
NetworkInterfaceStatsResponse,
SensorTemperatureResponse,
@ -38,6 +37,7 @@ from platypush.message.response.system import (
from platypush.plugins import action
from platypush.plugins.sensor import SensorPlugin
from platypush.schemas.system import (
ConnectionSchema,
CpuFrequency,
CpuFrequencySchema,
CpuInfo,
@ -299,50 +299,36 @@ class SystemPlugin(SensorPlugin, EntityManager):
return NetworkInterfaceSchema().dump(self._net_io_counters_avg())
@action
def net_connections(
self, type: Optional[str] = None
) -> Union[NetworkConnectionResponse, NetworkResponseList]:
def net_connections(self, type: str = 'inet') -> List[dict]:
"""
Get the list of active network connections.
On macOS this function requires root privileges.
On MacOS this function requires root privileges.
:param type: Connection type to filter. Supported types:
:param type: Connection type to filter (default: ``inet``). Supported
types:
+------------+----------------------------------------------------+
| Kind Value | Connections using |
| ``type`` | Description |
+------------+----------------------------------------------------+
| inet | IPv4 and IPv6 |
| inet4 | IPv4 |
| inet6 | IPv6 |
| tcp | TCP |
| tcp4 | TCP over IPv4 |
| tcp6 | TCP over IPv6 |
| udp | UDP |
| udp4 | UDP over IPv4 |
| udp6 | UDP over IPv6 |
| unix | UNIX socket (both UDP and TCP protocols) |
| all | the sum of all the possible families and protocols |
| ``inet`` | IPv4 and IPv6 |
| ``inet4`` | IPv4 |
| ``inet6`` | IPv6 |
| ``tcp`` | TCP |
| ``tcp4`` | TCP over IPv4 |
| ``tcp6`` | TCP over IPv6 |
| ``udp`` | UDP |
| ``udp4`` | UDP over IPv4 |
| ``udp6`` | UDP over IPv6 |
| ``unix`` | UNIX socket (both UDP and TCP protocols) |
| ``all`` | Any families and protocols |
+------------+----------------------------------------------------+
:return: List of :class:`platypush.message.response.system.NetworkConnectionResponse`.
:return: .. schema:: system.ConnectionSchema(many=True)
"""
conns = psutil.net_connections(kind=type)
return NetworkResponseList(
[
NetworkConnectionResponse(
fd=conn.fd,
family=conn.family.name,
type=conn.type.name,
local_address=conn.laddr[0] if conn.laddr else None,
local_port=conn.laddr[1] if len(conn.laddr) > 1 else None,
remote_address=conn.raddr[0] if conn.raddr else None,
remote_port=conn.raddr[1] if len(conn.raddr) > 1 else None,
status=conn.status,
pid=conn.pid,
)
for conn in conns
]
schema = ConnectionSchema()
return schema.dump(
schema.load(psutil.net_connections(kind=type), many=True), # type: ignore
many=True,
)
@action

View File

@ -1,3 +1,4 @@
from ._connection import Connection, ConnectionSchema
from ._cpu import (
Cpu,
CpuFrequency,
@ -17,6 +18,8 @@ from ._schemas import SystemInfoSchema
__all__ = [
"Connection",
"ConnectionSchema",
"Cpu",
"CpuFrequency",
"CpuFrequencySchema",

View File

@ -0,0 +1,4 @@
from ._model import Connection
from ._schemas import ConnectionSchema
__all__ = ['Connection', 'ConnectionSchema']

View File

@ -0,0 +1,41 @@
from marshmallow import pre_load
from platypush.schemas.dataclasses import DataClassSchema
class ConnectionBaseSchema(DataClassSchema):
"""
Base schema for connections.
"""
@pre_load
def pre_load(self, data, **_) -> dict:
if hasattr(data, '_asdict'):
data = data._asdict()
addr_mapping = {
'laddr': ('local_address', 'local_port'),
'raddr': ('remote_address', 'remote_port'),
}
# Parse laddr/raddr attributes
for ext_attr, (addr_attr, port_attr) in addr_mapping.items():
value = data.pop(ext_attr, None)
if not value:
data[addr_attr] = data[port_attr] = None
elif isinstance(value, tuple):
data[addr_attr], data[port_attr] = value
elif isinstance(value, str):
data[addr_attr] = value
data[port_attr] = None
# Handle enum values
for attr in ['type', 'family']:
value = data.pop(attr, None)
if value is not None:
data[attr] = value.name
if data.get('status') == 'NONE':
data['status'] = None
return data

View File

@ -0,0 +1,96 @@
from dataclasses import dataclass, field
from socket import AddressFamily, SocketKind
from typing import Optional
@dataclass
class Connection:
"""
Network/UNIX socket data class.
"""
fd: int = field(
metadata={
'metadata': {
'description': 'File descriptor',
'example': 3,
},
}
)
family: AddressFamily = field(
metadata={
'metadata': {
'description': 'Socket family',
'example': AddressFamily.AF_INET.name,
}
}
)
type: SocketKind = field(
metadata={
'metadata': {
'description': 'Socket type',
'example': SocketKind.SOCK_STREAM.name,
}
}
)
local_address: str = field(
metadata={
'metadata': {
'description': 'Local address, as an IP address for network '
'connections and a socket path for a UNIX socket',
'example': '192.168.1.2',
}
}
)
local_port: Optional[int] = field(
metadata={
'metadata': {
'description': 'Local port, if this is a TCP/UDP connection, '
'otherwise null',
'example': 12345,
}
}
)
remote_address: Optional[str] = field(
metadata={
'metadata': {
'description': 'Remote address, if this is a network '
'connection, otherwise null',
'example': '192.168.1.1',
}
}
)
remote_port: Optional[int] = field(
metadata={
'metadata': {
'description': 'Local port, if this is a TCP/UDP connection, '
'otherwise null',
'example': 443,
}
}
)
status: Optional[str] = field(
metadata={
'metadata': {
'description': 'Connection status, if this is a network '
'connection, otherise null',
'example': 'ESTABLISHED',
}
}
)
pid: Optional[int] = field(
metadata={
'metadata': {
'description': 'ID of the process that owns the connection',
'example': 4321,
}
}
)

View File

@ -0,0 +1,7 @@
from marshmallow_dataclass import class_schema
from ._base import ConnectionBaseSchema
from ._model import Connection
ConnectionSchema = class_schema(Connection, base_schema=ConnectionBaseSchema)