ktbyers / netmiko

Multi-vendor library to simplify Paramiko SSH connections to network devices
MIT License
3.49k stars 1.26k forks source link

Support for NEC Univerge IX Routers #3408

Open inaba-vdom-0 opened 2 months ago

inaba-vdom-0 commented 2 months ago

I added support for the NEC Univerge IX Router series. https://jpn.nec.com/univerge/ix/index.html

Test configuration:

test_devices.yml

nec_ix:
device_type: nec_ix
ip: 192.168.1.30
username: test-ix
password: testnecix123
session_log: 'netmiko_session.log'

commands.yml

nec_ix:
version: "show version"
config_mode_command: "configure"
basic: "show ip interface"
extended_output: "show running-config"
config:
- "logging buffered 4096"
- "logging timestamp datetime"
- "logging buffered 8192"
config_verification: "show running-config"
config_file: "nec_ix_commands.txt"

responses.yml

nec_ix:
base_prompt: Router
router_prompt : Router#
enable_prompt: Router(config)#
interface_ip: 192.168.1.30
version_banner: "IX Series"
multiple_line_output: "interface Null0.0"
file_check_cmd: "10.0.0.1/24"

nec_ix_commands.txt

interface GigaEthernet2.0
ip address 10.0.0.1/24
no shutdown

I will send the test results, but there are some issues.
test_result.log

vd-0@test-vm:~/develop/tests$ sh ./test_nec_ix.sh
Starting tests...good luck:
NEC Univerge IX Series
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-8.1.1, pluggy-1.4.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/vd-0/develop/tests
configfile: setup.cfg
collected 25 items

test_netmiko_show.py::test_failed_key SKIPPED (Not using SSH-keys)
test_netmiko_show.py::test_disable_paging PASSED
test_netmiko_show.py::test_terminal_width PASSED
test_netmiko_show.py::test_ssh_connect PASSED
test_netmiko_show.py::test_ssh_connect_cm PASSED
test_netmiko_show.py::test_send_command_timing PASSED
test_netmiko_show.py::test_send_command_timing_no_cmd_verify SKIPPED
test_netmiko_show.py::test_send_command PASSED
test_netmiko_show.py::test_send_command_no_cmd_verify SKIPPED
test_netmiko_show.py::test_complete_on_space_disabled SKIPPED
test_netmiko_show.py::test_send_command_textfsm SKIPPED (TextFSM/ntc-
templates not supported on this platform)
test_netmiko_show.py::test_send_command_ttp SKIPPED (TTP template not
existing for this platform)
test_netmiko_show.py::test_send_command_genie SKIPPED (Genie not
supported on this platform)
test_netmiko_show.py::test_send_multiline_timing SKIPPED
test_netmiko_show.py::test_send_multiline SKIPPED
test_netmiko_show.py::test_send_multiline_prompt SKIPPED
test_netmiko_show.py::test_send_multiline_simple SKIPPED
test_netmiko_show.py::test_base_prompt PASSED
test_netmiko_show.py::test_strip_prompt PASSED
test_netmiko_show.py::test_strip_command PASSED
test_netmiko_show.py::test_normalize_linefeeds PASSED
test_netmiko_show.py::test_clear_buffer PASSED
test_netmiko_show.py::test_enable_mode PASSED
test_netmiko_show.py::test_disconnect PASSED
test_netmiko_show.py::test_disconnect_no_enable SKIPPED

======================= 13 passed, 12 skipped in 26.54s ========================
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-8.1.1, pluggy-1.4.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/vd-0/develop/tests
configfile: setup.cfg
collected 13 items

test_netmiko_config.py::test_ssh_connect PASSED
test_netmiko_config.py::test_enable_mode PASSED
test_netmiko_config.py::test_config_mode PASSED
test_netmiko_config.py::test_exit_config_mode FAILED
test_netmiko_config.py::test_config_set PASSED
test_netmiko_config.py::test_config_set_generator PASSED
test_netmiko_config.py::test_config_set_longcommand PASSED
test_netmiko_config.py::test_config_hostname PASSED
test_netmiko_config.py::test_config_from_file PASSED
test_netmiko_config.py::test_config_error_pattern SKIPPED (No
error_pattern defined.)
test_netmiko_config.py::test_banner SKIPPED (No banner defined.)
test_netmiko_config.py::test_global_cmd_verify SKIPPED (No banner
defined.)
test_netmiko_config.py::test_disconnect PASSED

=================================== FAILURES ===================================
____________________________ test_exit_config_mode _____________________________

net_connect = <netmiko.nec.nec_ix.NecIxSSH object at 0x79eee58e11e0>
commands = {'basic': 'show ip interface', 'config': ['logging buffered 4096', 'logging timestamp datetime', 'logging buffered 8192'], 'config_file': 'nec_ix_commands.txt', 'config_mode_command': 'configure', ...}
expected_responses = {'base_prompt': 'Router', 'enable_prompt': 'Router(config)#', 'file_check_cmd': '10.0.0.1/24', 'interface_ip': '192.168.1.30', ...}

python
Copy code
def test_exit_config_mode(net_connect, commands, expected_responses):
    """Test exit config mode."""
    if net_connect._config_mode:
        net_connect.exit_config_mode()
python
Copy code
      assert net_connect.check_config_mode() is False
E assert True is False
E + where True = <bound method NecIxBase.check_config_mode of <netmiko.nec.nec_ix.NecIxSSH object at 0x79eee58e11e0>>()
E + where <bound method NecIxBase.check_config_mode of <netmiko.nec.nec_ix.NecIxSSH object at 0x79eee58e11e0>> = <netmiko.nec.nec_ix.NecIxSSH object at 0x79eee58e11e0>.check_config_mode

test_netmiko_config.py:50: AssertionError
=========================== short test summary info ============================
FAILED test_netmiko_config.py::test_exit_config_mode - assert True is False
=================== 1 failed, 9 passed, 3 skipped in 38.06s ====================
vd-0@test-vm:~/develop/tests$

I also conducted tests for autodetection.

logging.basicConfig(filename="netmiko_test-code.log", level=logging.DEBUG) logger = logging.getLogger("netmiko")

remote_device = { 'ip': '192.168.1.30', 'username': 'test-ix', 'password': 'testnecix123', 'device_type': 'autodetect', 'global_delay_factor': 3, 'session_log' : 'test-code.log', 'fast_cli' : False }

guesser = SSHDetect(**remote_device) best_match = guesser.autodetect() print(best_match) print(guesser.potential_matches)

remote_device['device_type'] = best_match connection = ConnectHandler(**remote_device)

result = connection.send_command(command_string='show version')

connection.disconnect()


test_result.log
```shell
vd-0@test-vm:~/develop/tests$  cd /home/vd-0/develop/tests ; /usr/bin/env /bin/python3 /home/vd-0/.vscode/extensions/ms-python.debugpy-2024.2.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher 51489 -- /home/vd-0/develop/tests/test_nec_ix/test-code-autodetect.py 
nec_ix
{'nec_ix': 99}

test-code.log

NEC Portable Internetwork Core Operating System Software
Copyright Notices:
Copyright (c) NEC Corporation 2001-2022. All rights reserved.
Copyright (c) 1985-1998 OpenROUTE Networks, Inc.
Copyright (c) 1984-1987, 1989 J. Noel Chiappa.
Router# 
Router# 
Router# 
Router# svintr-config
Enter configuration commands, one per line. End with CNTL/Z.
Router(config)# 
Router(config)# 
Router(config)# 
Router(config)# 
Router(config)# terminal length 0
Router(config)# 
Router(config)# show version
NEC Portable Internetwork Core Operating System Software
IX Series IX2215 (magellan-sec) Software, Version 10.7.18, RELEASE SOFTWARE
Compiled Oct 25-Tue-2022 12:37:13 JST #2 by sw-build, coregen-10.7(18)

ROM: System Bootstrap, Version 22.1
System Diagnostic, Version 22.1
Initialization Program, Version 3.1

System uptime is 4 days 23 hours 37 minutes
System woke up by reload, caused by power-on
System started at Mar 29-Fri-2024 22:13:10 JST
System image file is "ix2215-ms-10.7.18.ldc"

Processor board ID <0>
IX2215 (P1010E) processor with 262144K bytes of memory.
3 GigaEthernet/IEEE 802.3 interfaces
1 ISDN Basic Rate interface
1 USB interface
1024K bytes of non-volatile configuration memory.
32768K bytes of processor board System flash (Read/Write)
Router(config)# 

Issues:

exit_config_mode

test_exit_config_mode in test_netmiko_config.py, an error occurs because check_config_mode returns True after exit_config_mode. Although the configure command is used to return to the top menu of the configure mode from the interface configure mode, the prompt usually remains the same. Using the exit command will exit the configure mode, limiting the available show commands. Therefore, it is necessary to use the configure command as much as possible.

example command logging

Router(config-GigaEthernet2.0)#
Router(config-GigaEthernet2.0)# configure
Router(config)#
Router(config)# configure
Router(config)#
Router(config)# exit
Router#

Telnet driver

There is a bug in the Telnet driver. When performing telnet access, login fails and returns a "telnet connection closed" error. terminal log

vd-0@test-vm:~/develop/tests$ cd /home/vd-0/develop/tests ; /usr/bin/env /bin/python3 /home/vd-0/.vscode/extensions/ms-python.debugpy-2024.2.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher 43125 -- /home/vd-0/develop/tests/test_nec_ix/test-code.py
Traceback (most recent call last):
File "/home/vd-0/.local/lib/python3.10/site-packages/netmiko/nec/nec_ix.py", line 178, in telnet_login
output = self.read_channel()
File "/home/vd-0/.local/lib/python3.10/site-packages/netmiko/base_connection.py", line 97, in wrapper_decorator
return_val = func(self, *args, **kwargs)
File "/home/vd-0/.local/lib/python3.10/site-packages/netmiko/base_connection.py", line 628, in read_channel
new_data = self.channel.read_channel()
File "/home/vd-0/.local/lib/python3.10/site-packages/netmiko/channel.py", line 126, in read_channel
return self.remote_conn.read_very_eager().decode(self.encoding, "ignore")
File "/usr/lib/python3.10/telnetlib.py", line 368, in read_very_eager
return self.read_very_lazy()
File "/usr/lib/python3.10/telnetlib.py", line 405, in read_very_lazy
raise EOFError('telnet connection closed')
EOFError: telnet connection closed

When debugging with debug logs and breakpoints, it was found that the base_connection forcibly closes the session when executing output = self.read_channel(). Router debug logging reveals that the TELS.003: Disconnected client log is outputted at the timing of output = self.read_channel().

base_connection.py

while i <= max_loops:
try:
output = self.read_channel() # breakpoint
return_msg += output

bash
Copy code
            # Search for username pattern / send username
            if re.search(username_pattern, output, flags=re.I):

netmiko_debug.log

DEBUG:netmiko:write_channel: b'test-ix\r'
DEBUG:netmiko:read_channel: login:

Router debug log

Router# 2024/04/02 22:21:27 TELS.001: Connected client 192.168.1.8, server 192.168.1.30
2024/04/02 22:22:00 TELS.003: Disconnected client 192.168.1.8, server 192.168.1.30

These issues can be submitted as separate issues rather than as part of a pull request.
Thank you!

inaba-vdom-0 commented 2 months ago

I have fixed the Telnet bug by ignoring option negotiation to maintain the session.

class NecIxTelnet(NecIxBase):
    """NecIx Telnet driver."""

    def ignore_option_negotiation(self, sock, cmd, opt):
        pass

    def telnet_login(
            self,
            pri_prompt_terminator: str = r"#\s*$",
            alt_prompt_terminator: str = r">\s*$",
            username_pattern: str = r"login",
            pwd_pattern: str = r"assword",
            delay_factor: float = 1.0,
            max_loops: int = 20,
        ) -> str:
        self.remote_conn.set_option_negotiation_callback(self.ignore_option_negotiation)
        return super().telnet_login(
            pri_prompt_terminator = pri_prompt_terminator,
            alt_prompt_terminator = alt_prompt_terminator,
            username_pattern = username_pattern,
            pwd_pattern = pwd_pattern,
            delay_factor = delay_factor,
            max_loops = max_loops
        )

Router debug log

DEBUG:netmiko:read_channel: login: 
DEBUG:netmiko:write_channel: b'test-ix\r'
DEBUG:netmiko:read_channel: test-ix
Password: 
DEBUG:netmiko:write_channel: b'********\r'
DEBUG:netmiko:read_channel: 
NEC Portable Internetwork Core Operating System Software
Copyright Notices:
Copyright (c) NEC Corporation 2001-2022. All rights reserved.
Copyright (c) 1985-1998 OpenROUTE Networks, Inc.
Copyright (c) 1984-1987, 1989 J. Noel Chiappa.
Router# 
DEBUG:netmiko:write_channel: b'\r'
DEBUG:netmiko:read_channel: 
Router# 
DEBUG:netmiko:Clear buffer detects data in the channel
DEBUG:netmiko:read_channel: 
DEBUG:netmiko:write_channel: b'\r'
DEBUG:netmiko:read_channel: 
DEBUG:netmiko:read_channel: 
DEBUG:netmiko:read_channel: 
Router# 
DEBUG:netmiko:Pattern found: (\#) 
Router#
DEBUG:netmiko:read_channel: 
DEBUG:netmiko:Clear buffer detects data in the channel
DEBUG:netmiko:read_channel: 
DEBUG:netmiko:[find_prompt()]: prompt is Router#

I will conduct tests over the weekend and, if there are no issues, will submit the pull request again. Thank you for your continued support.

ktbyers commented 2 months ago

@inaba-vdom-0 Okay, let me know when this pull-request is ready and I will review (or when a new version of it is ready).

inaba-vdom-0 commented 2 months ago

@ktbyers I have recently modified the code and committed the tested code. Please check the code.