feat: Fingerprint-Attribute für Discovery-Scoring
Ergänzt DRIVER_NAME, HTTP_FINGERPRINT, SNMP_FINGERPRINT, SSH_FINGERPRINT, PORT_SPECS und SNMP_OBJECT_ID_PREFIX gemäß docs/DISCOVERY_FINGERPRINTING.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
13e1aba5aa
commit
23e68ecd87
@@ -11,7 +11,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -22,7 +22,7 @@ from cryptography.hazmat.backends import default_backend
|
|||||||
|
|
||||||
from napalm.base.exceptions import ConnectionClosedException, ConnectionException
|
from napalm.base.exceptions import ConnectionClosedException, ConnectionException
|
||||||
|
|
||||||
from napalm_device_types import ResidentialGatewayDriver
|
from napalm_device_types import FingerprintRule, ResidentialGatewayDriver
|
||||||
from napalm_device_types.models import (
|
from napalm_device_types.models import (
|
||||||
DeviceActionResultDict,
|
DeviceActionResultDict,
|
||||||
HostDict,
|
HostDict,
|
||||||
@@ -90,6 +90,13 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
"""NAPALM driver for Zyxel VMG residential gateway devices via DAL protocol."""
|
"""NAPALM driver for Zyxel VMG residential gateway devices via DAL protocol."""
|
||||||
|
|
||||||
VENDOR = "Zyxel"
|
VENDOR = "Zyxel"
|
||||||
|
DRIVER_NAME = "zyxel"
|
||||||
|
SNMP_OBJECT_ID_PREFIX = "1.3.6.1.4.1.890"
|
||||||
|
HTTP_FINGERPRINT = [
|
||||||
|
FingerprintRule("zyxel", weight=7.0, mandatory=True),
|
||||||
|
FingerprintRule("gs1920", weight=6.0),
|
||||||
|
FingerprintRule("gs2210", weight=6.0),
|
||||||
|
]
|
||||||
|
|
||||||
_LOGIN_PATH = "UserLogin"
|
_LOGIN_PATH = "UserLogin"
|
||||||
_LOGOUT_PATH = "cgi-bin/UserLogout"
|
_LOGOUT_PATH = "cgi-bin/UserLogout"
|
||||||
@@ -103,7 +110,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
username: str,
|
username: str,
|
||||||
password: str,
|
password: str,
|
||||||
timeout: int = 60,
|
timeout: int = 60,
|
||||||
optional_args: Optional[Dict[str, Any]] = None,
|
optional_args: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
self.username = username
|
self.username = username
|
||||||
@@ -117,9 +124,9 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
self.port = int(self.optional_args.get("port") or (443 if self.use_tls else 80))
|
self.port = int(self.optional_args.get("port") or (443 if self.use_tls else 80))
|
||||||
self.verify = bool(self.optional_args.get("verify", False))
|
self.verify = bool(self.optional_args.get("verify", False))
|
||||||
|
|
||||||
self._session: Optional[requests.Session] = None
|
self._session: requests.Session | None = None
|
||||||
self._sessionkey: Optional[str] = None
|
self._sessionkey: str | None = None
|
||||||
self._crypto: Optional[ZyxelCrypto] = None
|
self._crypto: ZyxelCrypto | None = None
|
||||||
self._base_url: str = (
|
self._base_url: str = (
|
||||||
f"{'https' if self.use_tls else 'http'}://{self.hostname}:{self.port}"
|
f"{'https' if self.use_tls else 'http'}://{self.hostname}:{self.port}"
|
||||||
)
|
)
|
||||||
@@ -194,7 +201,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
self._crypto = None
|
self._crypto = None
|
||||||
self._device_info = None
|
self._device_info = None
|
||||||
|
|
||||||
def is_alive(self) -> Dict[str, bool]:
|
def is_alive(self) -> dict[str, bool]:
|
||||||
"""Check if the device is reachable via TCP connection."""
|
"""Check if the device is reachable via TCP connection."""
|
||||||
try:
|
try:
|
||||||
sock = socket.create_connection((self.hostname, self.port), timeout=5)
|
sock = socket.create_connection((self.hostname, self.port), timeout=5)
|
||||||
@@ -203,7 +210,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
except OSError:
|
except OSError:
|
||||||
return {"is_alive": False}
|
return {"is_alive": False}
|
||||||
|
|
||||||
def _get_basic_info(self) -> Optional[dict]:
|
def _get_basic_info(self) -> dict | None:
|
||||||
if self._session is None:
|
if self._session is None:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@@ -213,7 +220,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
except (requests.RequestException, json.JSONDecodeError, TypeError):
|
except (requests.RequestException, json.JSONDecodeError, TypeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_rsa_pubkey(self) -> Optional[str]:
|
def _get_rsa_pubkey(self) -> str | None:
|
||||||
if self._session is None:
|
if self._session is None:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@@ -223,7 +230,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
except (requests.RequestException, json.JSONDecodeError, TypeError):
|
except (requests.RequestException, json.JSONDecodeError, TypeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _dal_query(self, oid: str, data: Optional[dict] = None) -> Optional[dict]:
|
def _dal_query(self, oid: str, data: dict | None = None) -> dict | None:
|
||||||
if self._session is None or self._sessionkey is None or self._crypto is None:
|
if self._session is None or self._sessionkey is None or self._crypto is None:
|
||||||
raise ConnectionClosedException("Not connected")
|
raise ConnectionClosedException("Not connected")
|
||||||
if data is None:
|
if data is None:
|
||||||
@@ -240,7 +247,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _fetch_page(self, path: str) -> Optional[str]:
|
def _fetch_page(self, path: str) -> str | None:
|
||||||
if self._session is None or self._sessionkey is None:
|
if self._session is None or self._sessionkey is None:
|
||||||
raise ConnectionClosedException("Not connected")
|
raise ConnectionClosedException("Not connected")
|
||||||
try:
|
try:
|
||||||
@@ -253,7 +260,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_facts(self) -> Dict[str, Any]:
|
def get_facts(self) -> dict[str, Any]:
|
||||||
"""Return device facts such as model, serial, OS version, and uptime."""
|
"""Return device facts such as model, serial, OS version, and uptime."""
|
||||||
info = self._device_info or {}
|
info = self._device_info or {}
|
||||||
try:
|
try:
|
||||||
@@ -271,9 +278,9 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
"interface_list": iface_list,
|
"interface_list": iface_list,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_interfaces(self) -> Dict[str, Dict[str, Any]]:
|
def get_interfaces(self) -> dict[str, dict[str, Any]]:
|
||||||
"""Return WAN, LAN, and WiFi interfaces from device info."""
|
"""Return WAN, LAN, and WiFi interfaces from device info."""
|
||||||
ifaces: Dict[str, Dict[str, Any]] = {}
|
ifaces: dict[str, dict[str, Any]] = {}
|
||||||
info = self._device_info or {}
|
info = self._device_info or {}
|
||||||
wan_ip = info.get("WanIp", "")
|
wan_ip = info.get("WanIp", "")
|
||||||
mac = info.get("MacAddress", "")
|
mac = info.get("MacAddress", "")
|
||||||
@@ -308,20 +315,20 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
}
|
}
|
||||||
return ifaces
|
return ifaces
|
||||||
|
|
||||||
def get_interfaces_ip(self) -> Dict[str, Dict[str, Any]]:
|
def get_interfaces_ip(self) -> dict[str, dict[str, Any]]:
|
||||||
"""Return IP addresses of WAN and LAN interfaces."""
|
"""Return IP addresses of WAN and LAN interfaces."""
|
||||||
info = self._device_info or {}
|
info = self._device_info or {}
|
||||||
wan_ip = info.get("WanIp", "")
|
wan_ip = info.get("WanIp", "")
|
||||||
lan_ip = info.get("LanIp", self.hostname)
|
lan_ip = info.get("LanIp", self.hostname)
|
||||||
result: Dict[str, Dict[str, Any]] = {}
|
result: dict[str, dict[str, Any]] = {}
|
||||||
if wan_ip:
|
if wan_ip:
|
||||||
result["wan"] = {"ipv4": {wan_ip: {"prefix_length": 24}}, "ipv6": {}}
|
result["wan"] = {"ipv4": {wan_ip: {"prefix_length": 24}}, "ipv6": {}}
|
||||||
result["lan"] = {"ipv4": {lan_ip: {"prefix_length": 24}}, "ipv6": {}}
|
result["lan"] = {"ipv4": {lan_ip: {"prefix_length": 24}}, "ipv6": {}}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_arp_table(self, vrf: str = "") -> List[Dict[str, Any]]:
|
def get_arp_table(self, vrf: str = "") -> list[dict[str, Any]]:
|
||||||
"""Return the ARP table derived from DHCP host entries."""
|
"""Return the ARP table derived from DHCP host entries."""
|
||||||
arp: List[Dict[str, Any]] = []
|
arp: list[dict[str, Any]] = []
|
||||||
for host in self.get_hosts():
|
for host in self.get_hosts():
|
||||||
if not host["is_active"] or not host["mac"] or not host["ip"]:
|
if not host["is_active"] or not host["mac"] or not host["ip"]:
|
||||||
continue
|
continue
|
||||||
@@ -333,11 +340,11 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
})
|
})
|
||||||
return arp
|
return arp
|
||||||
|
|
||||||
def get_lldp_neighbors(self) -> Dict[str, Any]:
|
def get_lldp_neighbors(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_lldp_neighbors_detail(self, interface: str = "") -> Dict[str, Any]:
|
def get_lldp_neighbors_detail(self, interface: str = "") -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -347,27 +354,27 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
full: bool = False,
|
full: bool = False,
|
||||||
sanitized: bool = False,
|
sanitized: bool = False,
|
||||||
format: str = "text",
|
format: str = "text",
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_environment(self) -> Dict[str, Any]:
|
def get_environment(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_optics(self) -> Dict[str, Any]:
|
def get_optics(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_ntp_peers(self) -> Dict[str, Optional[str]]:
|
def get_ntp_peers(self) -> dict[str, str | None]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_ntp_servers(self) -> Dict[str, Optional[str]]:
|
def get_ntp_servers(self) -> dict[str, str | None]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_ntp_stats(self) -> List[Dict[str, Any]]:
|
def get_ntp_stats(self) -> list[dict[str, Any]]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -381,7 +388,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
count: int = 5,
|
count: int = 5,
|
||||||
vrf: str = "",
|
vrf: str = "",
|
||||||
source_interface: str = "",
|
source_interface: str = "",
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Ping a destination via the device's DAL diagnostics."""
|
"""Ping a destination via the device's DAL diagnostics."""
|
||||||
result = self._dal_query("PINGTEST", {
|
result = self._dal_query("PINGTEST", {
|
||||||
"ProtocolVersion": "IPv4",
|
"ProtocolVersion": "IPv4",
|
||||||
@@ -409,7 +416,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
ttl: int = 255,
|
ttl: int = 255,
|
||||||
timeout: int = 2,
|
timeout: int = 2,
|
||||||
vrf: str = "",
|
vrf: str = "",
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {"success": {}, "error": "Not supported on Zyxel VMG"}
|
return {"success": {}, "error": "Not supported on Zyxel VMG"}
|
||||||
|
|
||||||
@@ -418,55 +425,55 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
destination: str = "",
|
destination: str = "",
|
||||||
protocol: str = "",
|
protocol: str = "",
|
||||||
longer: bool = False,
|
longer: bool = False,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_snmp_information(self) -> Dict[str, Any]:
|
def get_snmp_information(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_users(self) -> Dict[str, Any]:
|
def get_users(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_network_instances(self, name: str = "") -> Dict[str, Any]:
|
def get_network_instances(self, name: str = "") -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_bgp_config(self, group: str = "", neighbor: str = "") -> Dict[str, Any]:
|
def get_bgp_config(self, group: str = "", neighbor: str = "") -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_bgp_neighbors(self) -> Dict[str, Any]:
|
def get_bgp_neighbors(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_bgp_neighbors_detail(self, neighbor_address: str = "") -> Dict[str, Any]:
|
def get_bgp_neighbors_detail(self, neighbor_address: str = "") -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_probes_config(self) -> Dict[str, Any]:
|
def get_probes_config(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_probes_results(self) -> Dict[str, Any]:
|
def get_probes_results(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_mac_address_table(self) -> List[Dict[str, Any]]:
|
def get_mac_address_table(self) -> list[dict[str, Any]]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_firewall_policies(self) -> Dict[str, Any]:
|
def get_firewall_policies(self) -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def cli(self, commands: List[str], encoding: str = "text") -> Dict[str, Any]:
|
def cli(self, commands: list[str], encoding: str = "text") -> dict[str, Any]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def commit_config(self, message: str = "", revert_in: Optional[int] = None) -> None:
|
def commit_config(self, message: str = "", revert_in: int | None = None) -> None:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -475,13 +482,13 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def load_replace_candidate(
|
def load_replace_candidate(
|
||||||
self, filename: Optional[str] = None, config: Optional[str] = None
|
self, filename: str | None = None, config: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
raise NotImplementedError("Zyxel VMG does not support config replacement via NAPALM")
|
raise NotImplementedError("Zyxel VMG does not support config replacement via NAPALM")
|
||||||
|
|
||||||
def load_merge_candidate(
|
def load_merge_candidate(
|
||||||
self, filename: Optional[str] = None, config: Optional[str] = None
|
self, filename: str | None = None, config: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
raise NotImplementedError("Zyxel VMG does not support config merge via NAPALM")
|
raise NotImplementedError("Zyxel VMG does not support config merge via NAPALM")
|
||||||
@@ -510,7 +517,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
"link_status": "Up" if wan_ip else "Down",
|
"link_status": "Up" if wan_ip else "Down",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_port_forwards(self) -> List[PortForwardDict]:
|
def get_port_forwards(self) -> list[PortForwardDict]:
|
||||||
"""Return port forwarding rules from DAL or HTML fallback."""
|
"""Return port forwarding rules from DAL or HTML fallback."""
|
||||||
html = self._fetch_page("cgi-bin/DAL?oid=NAT_PORTMAP")
|
html = self._fetch_page("cgi-bin/DAL?oid=NAT_PORTMAP")
|
||||||
if html is not None:
|
if html is not None:
|
||||||
@@ -524,8 +531,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
return self._parse_port_forwards_html(html)
|
return self._parse_port_forwards_html(html)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _parse_port_forwards_dal(self, data: dict) -> List[PortForwardDict]:
|
def _parse_port_forwards_dal(self, data: dict) -> list[PortForwardDict]:
|
||||||
forwards: List[PortForwardDict] = []
|
forwards: list[PortForwardDict] = []
|
||||||
rules = data.get("rules", data.get("NATPortMap", data.get("entries", [])))
|
rules = data.get("rules", data.get("NATPortMap", data.get("entries", [])))
|
||||||
if isinstance(rules, dict):
|
if isinstance(rules, dict):
|
||||||
rules = [rules]
|
rules = [rules]
|
||||||
@@ -552,8 +559,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
re.I,
|
re.I,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_port_forwards_html(self, html: str) -> List[PortForwardDict]:
|
def _parse_port_forwards_html(self, html: str) -> list[PortForwardDict]:
|
||||||
forwards: List[PortForwardDict] = []
|
forwards: list[PortForwardDict] = []
|
||||||
for match in self._PORT_FORWARD_RE.finditer(html):
|
for match in self._PORT_FORWARD_RE.finditer(html):
|
||||||
forwards.append({
|
forwards.append({
|
||||||
"name": match.group("name").strip(),
|
"name": match.group("name").strip(),
|
||||||
@@ -565,7 +572,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
})
|
})
|
||||||
return forwards
|
return forwards
|
||||||
|
|
||||||
def get_hosts(self) -> List[HostDict]:
|
def get_hosts(self) -> list[HostDict]:
|
||||||
"""Return DHCP host entries from DAL or HTML fallback."""
|
"""Return DHCP host entries from DAL or HTML fallback."""
|
||||||
html = self._fetch_page("cgi-bin/DAL?oid=LAN_HOSTS")
|
html = self._fetch_page("cgi-bin/DAL?oid=LAN_HOSTS")
|
||||||
if html is not None:
|
if html is not None:
|
||||||
@@ -579,8 +586,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
return self._parse_hosts_html(html)
|
return self._parse_hosts_html(html)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _parse_hosts_dal(self, data: dict) -> List[HostDict]:
|
def _parse_hosts_dal(self, data: dict) -> list[HostDict]:
|
||||||
hosts: List[HostDict] = []
|
hosts: list[HostDict] = []
|
||||||
entries = data.get("hosts", data.get("LANHosts", data.get("entries", [])))
|
entries = data.get("hosts", data.get("LANHosts", data.get("entries", [])))
|
||||||
if isinstance(entries, dict):
|
if isinstance(entries, dict):
|
||||||
entries = [entries]
|
entries = [entries]
|
||||||
@@ -610,8 +617,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
r"(?P<hostname>\S+)?",
|
r"(?P<hostname>\S+)?",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_hosts_html(self, html: str) -> List[HostDict]:
|
def _parse_hosts_html(self, html: str) -> list[HostDict]:
|
||||||
hosts: List[HostDict] = []
|
hosts: list[HostDict] = []
|
||||||
for match in self._HOST_RE.finditer(html):
|
for match in self._HOST_RE.finditer(html):
|
||||||
hosts.append({
|
hosts.append({
|
||||||
"mac": match.group("mac").lower(),
|
"mac": match.group("mac").lower(),
|
||||||
@@ -622,10 +629,10 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
})
|
})
|
||||||
return hosts
|
return hosts
|
||||||
|
|
||||||
def get_nat_translations(self) -> List[NATTranslationDict]:
|
def get_nat_translations(self) -> list[NATTranslationDict]:
|
||||||
"""Return NAT translations derived from enabled port forwards."""
|
"""Return NAT translations derived from enabled port forwards."""
|
||||||
wan_ip = (self._device_info or {}).get("WanIp", "")
|
wan_ip = (self._device_info or {}).get("WanIp", "")
|
||||||
translations: List[NATTranslationDict] = []
|
translations: list[NATTranslationDict] = []
|
||||||
for forward in self.get_port_forwards():
|
for forward in self.get_port_forwards():
|
||||||
if not forward["enabled"]:
|
if not forward["enabled"]:
|
||||||
continue
|
continue
|
||||||
@@ -639,11 +646,11 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
})
|
})
|
||||||
return translations
|
return translations
|
||||||
|
|
||||||
def get_vpn_tunnels(self) -> Dict[str, VPNTunnelDict]:
|
def get_vpn_tunnels(self) -> dict[str, VPNTunnelDict]:
|
||||||
"""Not supported on Zyxel VMG."""
|
"""Not supported on Zyxel VMG."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_wireless_clients(self) -> List[WirelessClientDict]:
|
def get_wireless_clients(self) -> list[WirelessClientDict]:
|
||||||
"""Return connected wireless clients from DAL or HTML fallback."""
|
"""Return connected wireless clients from DAL or HTML fallback."""
|
||||||
html = self._fetch_page("cgi-bin/DAL?oid=WIFI_CLIENTS")
|
html = self._fetch_page("cgi-bin/DAL?oid=WIFI_CLIENTS")
|
||||||
if html is not None:
|
if html is not None:
|
||||||
@@ -657,8 +664,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
return self._parse_wireless_clients_html(html)
|
return self._parse_wireless_clients_html(html)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _parse_wireless_clients_dal(self, data: dict) -> List[WirelessClientDict]:
|
def _parse_wireless_clients_dal(self, data: dict) -> list[WirelessClientDict]:
|
||||||
clients: List[WirelessClientDict] = []
|
clients: list[WirelessClientDict] = []
|
||||||
entries = data.get("clients", data.get("assoc", data.get("entries", [])))
|
entries = data.get("clients", data.get("assoc", data.get("entries", [])))
|
||||||
if isinstance(entries, dict):
|
if isinstance(entries, dict):
|
||||||
entries = [entries]
|
entries = [entries]
|
||||||
@@ -691,8 +698,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
r"(?P<rssi>-?\d+)",
|
r"(?P<rssi>-?\d+)",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_wireless_clients_html(self, html: str) -> List[WirelessClientDict]:
|
def _parse_wireless_clients_html(self, html: str) -> list[WirelessClientDict]:
|
||||||
clients: List[WirelessClientDict] = []
|
clients: list[WirelessClientDict] = []
|
||||||
for match in self._WIFI_CLIENT_RE.finditer(html):
|
for match in self._WIFI_CLIENT_RE.finditer(html):
|
||||||
clients.append({
|
clients.append({
|
||||||
"mac": match.group("mac").lower(),
|
"mac": match.group("mac").lower(),
|
||||||
@@ -707,7 +714,7 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
})
|
})
|
||||||
return clients
|
return clients
|
||||||
|
|
||||||
def get_ssids(self) -> Dict[str, SSIDDict]:
|
def get_ssids(self) -> dict[str, SSIDDict]:
|
||||||
"""Return WiFi SSID configurations from DAL or HTML fallback."""
|
"""Return WiFi SSID configurations from DAL or HTML fallback."""
|
||||||
html = self._fetch_page("cgi-bin/DAL?oid=WIFI_SSIDS")
|
html = self._fetch_page("cgi-bin/DAL?oid=WIFI_SSIDS")
|
||||||
if html is not None:
|
if html is not None:
|
||||||
@@ -721,8 +728,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
return self._parse_ssids_html(html)
|
return self._parse_ssids_html(html)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _parse_ssids_dal(self, data: dict) -> Dict[str, SSIDDict]:
|
def _parse_ssids_dal(self, data: dict) -> dict[str, SSIDDict]:
|
||||||
ssids: Dict[str, SSIDDict] = {}
|
ssids: dict[str, SSIDDict] = {}
|
||||||
entries = data.get("ssids", data.get("entries", []))
|
entries = data.get("ssids", data.get("entries", []))
|
||||||
if isinstance(entries, dict):
|
if isinstance(entries, dict):
|
||||||
entries = [entries]
|
entries = [entries]
|
||||||
@@ -744,8 +751,8 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
}
|
}
|
||||||
return ssids
|
return ssids
|
||||||
|
|
||||||
def _parse_ssids_html(self, html: str) -> Dict[str, SSIDDict]:
|
def _parse_ssids_html(self, html: str) -> dict[str, SSIDDict]:
|
||||||
ssids: Dict[str, SSIDDict] = {}
|
ssids: dict[str, SSIDDict] = {}
|
||||||
pattern = re.compile(r"SSID[^:]*:\s*(\S+)", re.I)
|
pattern = re.compile(r"SSID[^:]*:\s*(\S+)", re.I)
|
||||||
for match in pattern.finditer(html):
|
for match in pattern.finditer(html):
|
||||||
name = match.group(1)
|
name = match.group(1)
|
||||||
@@ -760,9 +767,9 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
|
|||||||
}
|
}
|
||||||
return ssids
|
return ssids
|
||||||
|
|
||||||
def get_radio_status(self) -> Dict[str, RadioStatusDict]:
|
def get_radio_status(self) -> dict[str, RadioStatusDict]:
|
||||||
"""Return WiFi radio status from DAL or HTML fallback."""
|
"""Return WiFi radio status from DAL or HTML fallback."""
|
||||||
radios: Dict[str, RadioStatusDict] = {}
|
radios: dict[str, RadioStatusDict] = {}
|
||||||
html = self._fetch_page("cgi-bin/DAL?oid=WIFI_RADIO")
|
html = self._fetch_page("cgi-bin/DAL?oid=WIFI_RADIO")
|
||||||
if html is not None:
|
if html is not None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user