Refactor Keenetic model to utilize centralized UTF-8 decoding utility

- Removed the internal `_decode_utf` method from the `Keenetic` class and replaced its usage with the new `decode_utf` utility function for decoding interface descriptions.
- Added a new configuration file `config.conf` for Keenetic devices to facilitate testing.
- Introduced an expected output JSON file `config.expected.json` to validate the parsing of Keenetic configurations against expected results.
This commit is contained in:
IluaAir
2026-06-07 08:41:59 +03:00
parent d329ddc4ad
commit 168111e23c
3 changed files with 505 additions and 21 deletions

View File

@@ -1,24 +1,13 @@
from ipaddress import ip_interface from ipaddress import ip_interface
from oxi.interfaces import register_parser from oxi.interfaces import register_parser
from oxi.interfaces.base import BaseDevice from oxi.interfaces.base import BaseDevice
from oxi.interfaces.utils import decode_utf
@register_parser(["NDMS", "keenetic", "KeeneticOS"]) @register_parser(["NDMS", "keenetic", "KeeneticOS"])
class Keenetic(BaseDevice): class Keenetic(BaseDevice):
template = "keenetic.ttp" template = "keenetic.ttp"
def _decode_utf(self, text: str):
if "\\x" in text:
desc = text.strip('"')
decoded = (
desc.encode("utf-8")
.decode("unicode_escape")
.encode("latin1")
.decode("utf-8")
)
return decoded
return text
def interfaces(self): def interfaces(self):
interfaces: list[dict] = self.raw["interfaces"] interfaces: list[dict] = self.raw["interfaces"]
for item in interfaces: for item in interfaces:
@@ -29,7 +18,7 @@ class Keenetic(BaseDevice):
item["mask"] = ipaddress.network.prefixlen item["mask"] = ipaddress.network.prefixlen
item.pop("netmask", "Key not found") item.pop("netmask", "Key not found")
if item.get("description"): if item.get("description"):
decoded = self._decode_utf(item.get("description", "")) decoded = decode_utf(item.get("description", ""))
item["description"] = decoded item["description"] = decoded
return interfaces return interfaces
@@ -37,13 +26,6 @@ class Keenetic(BaseDevice):
vlans = self.raw["vlans"] vlans = self.raw["vlans"]
for item in vlans: for item in vlans:
if item.get("description"): if item.get("description"):
decoded = self._decode_utf(item.get("description", "")) decoded = decode_utf(item.get("description", ""))
item["description"] = decoded item["description"] = decoded
return vlans return vlans
if __name__ == "__main__":
with open("./test2.txt") as file:
data = file.read()
mikr = Keenetic(data)
print(mikr.parse().model_dump_json())

341
tests/fixtures/keenetic/config.conf vendored Normal file
View File

@@ -0,0 +1,341 @@
!
! release: 4.03.C.6.2-7
! sandbox: stable
! title: 4.3.6.2
! arch: mips
!
! ndm:
! exact: 0-a3057529fd
! cdate: 29 Sep 2025
!
! bsp:
! exact: 0-03b50470c4
! cdate: 30 Sep 2025
!
! ndw:
! features: dual_image,led_control,wifi_button,wifi5ghz,
! vht2ghz,mimo2ghz,mimo5ghz,atf2ghz,atf5ghz,wifi6,wifi_ft,
! wpa3,hwnat
! components: base,cloudcontrol,corewireless,ddns,dhcpd,
! dns-filter,dns-https,dns-tls,dot1x,easyconfig,igmp,ip6,
! lang-en,lang-ru,miniupnpd,mws,nathelper-ftp,nathelper-
! h323,nathelper-pptp,nathelper-rtsp,nathelper-sip,ndmp,
! ndns,openvpn,pingcheck,ppe,pppoe,pptp,ssh,trafficcontrol,
! wireguard
!
! ndw3:
! version: 1.101.18.1
!
! ndw4:
! version: 4.3.C.6.2
!
! manufacturer: Keenetic Ltd.
! vendor: Keenetic
! series: KN
! model: Sprinter (KN-3710)
! hw_version: 7777777
! hw_type: router
! hw_id: KN-3710
! device: Sprinter
! region: EA
! description: Keenetic Sprinter (KN-3710)
! $$$ Agent: http/rci
! $$$ Last change: Fri, 3 Oct 2025 18:37:40 GMT
! $$$ Model: Keenetic Sprinter
! $$$ Username: admin
! $$$ Version: 2.06.1
system
set net.ipv4.ip_forward 1
set net.ipv4.neigh.default.gc_thresh1 256
set net.ipv4.neigh.default.gc_thresh2 1024
set net.ipv4.neigh.default.gc_thresh3 2048
set net.ipv4.tcp_fin_timeout 30
set net.ipv4.tcp_keepalive_time 120
set net.ipv6.conf.all.forwarding 1
set net.ipv6.neigh.default.gc_thresh1 256
set net.ipv6.neigh.default.gc_thresh2 1024
set net.ipv6.neigh.default.gc_thresh3 2048
set net.netfilter.nf_conntrack_tcp_timeout_established 1200
set vm.overcommit_memory 0
set vm.vfs_cache_pressure 1000
clock timezone Europe/Berlin
domainname WORKGROUP
hostname test_HW
caption default
description "Keenetic Sprinter (KN-3710)"
ndss dump-report disable
!
dyndns profile _WEBADMIN
!
interface GigabitEthernet0
up
!
interface GigabitEthernet0/1
rename 1
switchport mode access
switchport access vlan 1
up
!
interface GigabitEthernet0/2
rename 2
switchport mode access
switchport access vlan 1
up
!
interface GigabitEthernet0/3
rename 3
switchport mode access
switchport access vlan 1
up
!
interface GigabitEthernet0/Vlan1
description "Home VLAN"
ip dhcp client dns-routes
ip name-servers
up
!
interface GigabitEthernet0/Vlan2
rename ISP
description "\xd0\x9f\xd0\xbe\xd0\xb4\xd0\xba\xd0\xbb\xd1\x8e\xd1\x87\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5 Ethernet"
dyndns nobind
mac address factory wan
security-level public
ip address dhcp
ip dhcp client hostname test_HW
ip dhcp client dns-routes
ip mtu 1500
ip access-group _WEBADMIN_ISP in
ip global 57342
ip no name-servers
igmp upstream
ipv6 address auto
ipv6 prefix auto
ipv6 no name-servers auto
up
!
interface GigabitEthernet0/0
rename 0
role inet for ISP
switchport mode access
switchport access vlan 2
up
!
interface GigabitEthernet0/Vlan3
dyndns nobind
ip dhcp client dns-routes
ip name-servers
up
!
interface WifiMaster0
country-code RU
compatibility BGN+AX
rekey-interval 86400
up
!
interface WifiMaster0/AccessPoint0
mac access-list type none
authentication wpa-psk ns3 7777ggggddddsss
encryption enable
encryption wpa2
ip dhcp client dns-routes
ssid test_HW_2.4G
up
!
interface WifiMaster0/AccessPoint1
mac access-list type none
security-level private
encryption no enable
ip dhcp client dns-routes
down
!
interface WifiMaster0/AccessPoint2
mac access-list type none
security-level private
encryption no enable
ip dhcp client dns-routes
down
!
interface WifiMaster0/WifiStation0
security-level public
encryption no enable
ip dhcp client dns-routes
standby enable
standby timeout 600
down
!
interface WifiMaster1
country-code RU
compatibility AN+AC+AX
channel width 40-above/80
rekey-interval 86400
up
!
interface WifiMaster1/AccessPoint0
mac access-list type none
authentication wpa-psk ns3 7777ggggddddsss
encryption enable
encryption wpa2
ip dhcp client dns-routes
ssid test_HW_5G
up
!
interface WifiMaster1/AccessPoint1
mac access-list type none
security-level private
encryption no enable
ip dhcp client dns-routes
down
!
interface WifiMaster1/AccessPoint2
mac access-list type none
security-level private
encryption no enable
ip dhcp client dns-routes
down
!
interface WifiMaster1/WifiStation0
security-level public
encryption no enable
ip dhcp client dns-routes
standby enable
standby timeout 600
down
!
interface Bridge0
rename Home
description "Home network"
dyndns nobind
include GigabitEthernet0/Vlan1
include WifiMaster0/AccessPoint0
include WifiMaster1/AccessPoint0
mac access-list type none
security-level private
ip address 17.36.1.1 255.255.255.0
ip dhcp client dns-routes
ip access-group _WEBADMIN_Home in
ip name-servers
band-steering
up
!
interface Bridge1
rename Guest
description "Guest network"
traffic-shape rate 5120
dyndns nobind
include GigabitEthernet0/Vlan3
mac access-list type none
peer-isolation
security-level protected
ip address 10.1.30.1 255.255.255.0
ip dhcp client dns-routes
ip name-servers
down
!
interface Bridge2
rename Test
mac access-list type none
security-level public
ip dhcp client dns-routes
up
!
interface OpenVPN0
description test_HW-udp
role misc
security-level public
ip dhcp client dns-routes
ip tcp adjust-mss pmtu
ip name-servers
ipv6 name-servers auto
openvpn accept-routes
openvpn connect
up
!
interface OpenVPN2
description test_HW-tcp
role misc
dyndns nobind
security-level public
ip dhcp client dns-routes
ip tcp adjust-mss pmtu
openvpn accept-routes
openvpn connect
down
!
interface Wireguard0
description test_HW
dyndns nobind
security-level public
ip address 10.3.100.1 255.255.255.0
ip mtu 1324
ip tcp adjust-mss pmtu
wireguard listen-port 65513
wireguard peer 7777ggggddddsss= !test_HW
allow-ips 0.0.0.0 0.0.0.0
connect
!
up
!
interface Wireguard1
description test_HW
dyndns nobind
security-level private
ip address 10.1.100.1 255.255.255.0
ip mtu 1324
ip access-group _WEBADMIN_Wireguard1 in
ip tcp adjust-mss pmtu
wireguard listen-port 65511
wireguard peer 7777ggggddddsss= !test_HW
allow-ips 10.1.100.0 255.255.255.0
allow-ips 17.36.3.0 255.255.255.0
allow-ips 17.36.1.0 255.255.255.0
allow-ips 0.0.0.0 0.0.0.0
connect
!
up
!
interface Wireguard2
description test_HW
dyndns nobind
security-level private
ip address 10.2.100.1 255.255.255.0
ip access-group _WEBADMIN_Wireguard2 in
ip tcp adjust-mss pmtu
wireguard listen-port 65512
wireguard peer 7777ggggddddsss= !test_HW
allow-ips 0.0.0.0 0.0.0.0
connect
!
up
!
ip ssh
port 22
security-level public
lockout-policy 5 15 3
!
ip hotspot
policy Home permit
host 7777ggggddddsss permit
host 7777ggggddddsss priority 4
!
ipv6 subnet Default
bind Home
mode slaac
prefix length 64
number 0
!
ppe software
ppe hardware
upnp lan Home
service dhcp
service dns-proxy
service http
service telnet
service ssh
service ntp
service upnp
!
easyconfig disable
components
auto-update disable
auto-update channel stable
!

View File

@@ -0,0 +1,161 @@
{
"system": {
"model": "Sprinter (KN-3710)",
"serial_number": "7777777",
"version": "4.03.C.6.2-7"
},
"interfaces": [
{
"interface": "GigabitEthernet0",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "GigabitEthernet0/1",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "GigabitEthernet0/2",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "GigabitEthernet0/3",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "GigabitEthernet0/0",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster0",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster0/AccessPoint0",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster0/AccessPoint1",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster0/AccessPoint2",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster0/WifiStation0",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster1",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster1/AccessPoint0",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster1/AccessPoint1",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster1/AccessPoint2",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "WifiMaster1/WifiStation0",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "Bridge0",
"ip_address": "17.36.1.1",
"mask": 24,
"description": "Home network"
},
{
"interface": "Bridge1",
"ip_address": "10.1.30.1",
"mask": 24,
"description": "Guest network"
},
{
"interface": "Bridge2",
"ip_address": null,
"mask": null,
"description": null
},
{
"interface": "OpenVPN0",
"ip_address": null,
"mask": null,
"description": "test_HW-udp"
},
{
"interface": "OpenVPN2",
"ip_address": null,
"mask": null,
"description": "test_HW-tcp"
},
{
"interface": "Wireguard0",
"ip_address": "10.3.100.1",
"mask": 24,
"description": "test_HW"
},
{
"interface": "Wireguard1",
"ip_address": "10.1.100.1",
"mask": 24,
"description": "test_HW"
},
{
"interface": "Wireguard2",
"ip_address": "10.2.100.1",
"mask": 24,
"description": "test_HW"
}
],
"vlans": [
{
"vlan_id": 1,
"description": "Home VLAN"
},
{
"vlan_id": 2,
"description": "Подключение Ethernet"
},
{
"vlan_id": 3,
"description": "Home network"
}
]
}