You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

362 lines
14 KiB

# -*- coding: utf-8 -*-
"""UPnP IGD module."""
from datetime import timedelta
from ipaddress import IPv4Address
import logging
from typing import List, NamedTuple, Optional
from async_upnp_client.profile import UpnpProfileDevice
_LOGGER = logging.getLogger(__name__)
CommonLinkProperties = NamedTuple(
'CommonLinkProperties', [
('wan_access_type', str),
('layer1_upstream_max_bit_rate', int),
('layer1_downstream_max_bit_rate', int),
('physical_link_status', str)])
ConnectionTypeInfo = NamedTuple(
'ConnectionTypeInfo', [
('connection_type', str),
('possible_connection_types', str)])
StatusInfo = NamedTuple(
'StatusInfo', [
('connection_status', str),
('last_connection_error', str),
('uptime', int)])
NatRsipStatusInfo = NamedTuple(
'NatRsipStatusInfo', [
('nat_enabled', bool),
('rsip_available', bool)])
PortMappingEntry = NamedTuple(
'PortMappingEntry', [
('remote_host', Optional[IPv4Address]),
('external_port', int),
('protocol', str),
('internal_port', int),
('internal_client', IPv4Address),
('enabled', bool),
('description', str),
('lease_duration', Optional[timedelta])])
class IgdDevice(UpnpProfileDevice):
"""Representation of a IGD device."""
# pylint: disable=too-many-public-methods
DEVICE_TYPES = [
'urn:schemas-upnp-org:device:InternetGatewayDevice:1',
'urn:schemas-upnp-org:device:InternetGatewayDevice:2',
]
_SERVICE_TYPES = {
'WANPPPC': {
'urn:schemas-upnp-org:service:WANPPPConnection:1',
},
'WANIPC': {
'urn:schemas-upnp-org:service:WANIPConnection:1',
},
'WANCIC': {
'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
},
'L3FWD': {
'urn:schemas-upnp-org:service:Layer3Forwarding:1',
},
}
def _any_action(self, service_names: List[str], action_name: str):
for service_name in service_names:
action = self._action(service_name, action_name)
if action is not None:
return action
return None
async def async_get_total_bytes_received(self) -> int:
"""Get total bytes received."""
action = self._action('WANCIC', 'GetTotalBytesReceived')
result = await action.async_call()
return result['NewTotalBytesReceived']
async def async_get_total_bytes_sent(self) -> int:
"""Get total bytes sent."""
action = self._action('WANCIC', 'GetTotalBytesSent')
result = await action.async_call()
return result['NewTotalBytesSent']
async def async_get_total_packets_received(self) -> int:
"""Get total packets received."""
# pylint: disable=invalid-name
action = self._action('WANCIC', 'GetTotalPacketsReceived')
result = await action.async_call()
return result['NewTotalPacketsReceived']
async def async_get_total_packets_sent(self) -> int:
"""Get total packets sent."""
action = self._action('WANCIC', 'GetTotalPacketsSent')
result = await action.async_call()
return result['NewTotalPacketsSent']
async def async_get_enabled_for_internet(self) -> bool:
"""Get internet access enabled state."""
action = self._action('WANCIC', 'GetEnabledForInternet')
result = await action.async_call()
return result['NewEnabledForInternet']
async def async_set_enabled_for_internet(self, enabled: bool) -> None:
"""
Set internet access enabled state.
:param enabled whether access should be enabled
"""
action = self._action('WANCIC', 'SetEnabledForInternet')
await action.async_call(NewEnabledForInternet=enabled)
async def async_get_common_link_properties(self) -> CommonLinkProperties:
"""Get common link properties."""
# pylint: disable=invalid-name
action = self._action('WANCIC', 'GetCommonLinkProperties')
result = await action.async_call()
return CommonLinkProperties(
result['NewWANAccessType'],
result['NewLayer1UpstreamMaxBitRate'],
result['NewLayer1DownstreamMaxBitRate'],
result['NewPhysicalLinkStatus'])
async def async_get_external_ip_address(self, services: List = None) -> str:
"""
Get the external IP address.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'GetExternalIPAddress')
result = await action.async_call()
return result['NewExternalIPAddress']
async def async_get_generic_port_mapping_entry(self,
port_mapping_index: int,
services: List = None) -> PortMappingEntry:
"""
Get generic port mapping entry.
:param port_mapping_index Index of port mapping entry
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
# pylint: disable=invalid-name
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'GetGenericPortMappingEntry')
result = await action.async_call(
NewPortMappingIndex=port_mapping_index)
return PortMappingEntry(
IPv4Address(result['NewRemoteHost']) if result['NewRemoteHost'] else None,
result['NewExternalPort'],
result['NewProtocol'],
result['NewInternalPort'],
IPv4Address(result['NewInternalClient']),
result['NewEnabled'],
result['NewPortMappingDescription'],
timedelta(seconds=result['NewLeaseDuration']) if result['NewLeaseDuration'] else None)
async def async_get_specific_port_mapping_entry(self,
remote_host: Optional[IPv4Address],
external_port: int,
protocol: str,
services: List = None) -> PortMappingEntry:
"""
Get specific port mapping entry.
:param remote_host Address of remote host or None
:param external_port External port
:param protocol Protocol, 'TCP' or 'UDP'
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
# pylint: disable=invalid-name
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'GetSpecificPortMappingEntry')
result = await action.async_call(
NewRemoteHost=remote_host.exploded if remote_host else '',
NewExternalPort=external_port,
NewProtocol=protocol)
return PortMappingEntry(
remote_host,
external_port,
protocol,
result['NewInternalPort'],
IPv4Address(result['NewInternalClient']),
result['NewEnabled'],
result['NewPortMappingDescription'],
timedelta(seconds=result['NewLeaseDuration']) if result['NewLeaseDuration'] else None)
async def async_add_port_mapping(self,
remote_host: IPv4Address,
external_port: int,
protocol: str,
internal_port: int,
internal_client: IPv4Address,
enabled: bool,
description: str,
lease_duration: timedelta,
services: List = None):
"""
Add a port mapping.
:param remote_host Address of remote host or None
:param external_port External port
:param protocol Protocol, 'TCP' or 'UDP'
:param internal_port Internal port
:param internal_client Address of internal host
:param enabled Port mapping enabled
:param description Description for port mapping
:param lease_duration Lease duration
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
# pylint: disable=too-many-arguments
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'AddPortMapping')
await action.async_call(
NewRemoteHost=remote_host.exploded if remote_host else '',
NewExternalPort=external_port,
NewProtocol=protocol,
NewInternalPort=internal_port,
NewInternalClient=internal_client.exploded,
NewEnabled=enabled,
NewPortMappingDescription=description,
NewLeaseDuration=int(lease_duration.seconds) if lease_duration else 0)
async def async_delete_port_mapping(self,
remote_host: IPv4Address,
external_port: int,
protocol: str,
services: List = None):
"""
Delete an existing port mapping.
:param remote_host Address of remote host or None
:param external_port External port
:param protocol Protocol, 'TCP' or 'UDP'
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'DeletePortMapping')
await action.async_call(
NewRemoteHost=remote_host.exploded if remote_host else '',
NewExternalPort=external_port,
NewProtocol=protocol)
async def async_get_connection_type_info(self, services: List = None) -> ConnectionTypeInfo:
"""
Get connection type info.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'GetConnectionTypeInfo')
result = await action.async_call()
return ConnectionTypeInfo(
result['NewConnectionType'],
result['NewPossibleConnectionTypes'])
async def async_set_connection_type(self, connection_type: str, services: List = None) -> None:
"""
Set connection type.
:param connection_type connection type
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'SetConnectionType')
await action.async_call(NewConnectionType=connection_type)
async def async_request_connection(self, services: List = None) -> None:
"""
Request connection.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'RequestConnection')
await action.async_call()
async def async_request_termination(self, services: List = None) -> None:
"""
Request connection termination.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'RequestTermination')
await action.async_call()
async def async_force_termination(self, services: List = None) -> None:
"""
Force connection termination.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._action(services, 'ForceTermination')
await action.async_call()
async def async_get_status_info(self, services: List = None) -> StatusInfo:
"""
Get status info.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'GetStatusInfo')
result = await action.async_call()
return StatusInfo(
result['NewConnectionStatus'],
result['NewLastConnectionError'],
result['NewUptime'])
async def async_get_port_mapping_number_of_entries(self, services: List = None) -> int:
"""
Get number of port mapping entries.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
# pylint: disable=invalid-name
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'GetPortMappingNumberOfEntries')
result = await action.async_call()
return result['NewPortMappingNumberOfEntries']
async def async_get_nat_rsip_status(self, services: List = None) -> NatRsipStatusInfo:
"""
Get NAT enabled and RSIP availability statuses.
:param services List of service names to try to get action from, defaults to [WANIPC,WANPPP]
"""
services = services or ['WANIPC', 'WANPPP']
action = self._any_action(services, 'GetNATRSIPStatus')
result = await action.async_call()
return NatRsipStatusInfo(
result['NewNATEnabled'],
result['NewRSIPAvailable'])
async def async_get_default_connection_service(self) -> str:
"""Get default connection service."""
# pylint: disable=invalid-name
action = self._action('L3FWD', 'GetDefaultConnectionService')
result = await action.async_call()
return result['NewDefaultConnectionService']
async def async_set_default_connection_service(self, service: str) -> None:
"""
Set default connection service.
:param service default connection service
"""
# pylint: disable=invalid-name
action = self._action('L3FWD', 'SetDefaultConnectionService')
await action.async_call(NewDefaultConnectionService=service)