Search code examples
pythontelnettelnetlibnetmikonapalm

Telnet device automation


I try to connect to devices with python using telnet protocole to do some automation (for example somes old Cisco routers), For that i am using Napalm library (which based on Napalm library which based on telnetlib library)

The problem is that when i use directly telnetlib library it's works fine, but when i use Napalm or Netmiko it gives this error: get Telnet login failed.

Doeas anyone had this situation before?

PS: i tried some solution find in internet but nothing works.

Thank you in advance.

This code works (telnetlib library):

import telnetlib
import time
from pprint import pprint


def to_bytes(line):
    return f"{line}\n".encode("utf-8")


def send_show_command(ip, username, password, enable, commands):
    with telnetlib.Telnet(ip) as telnet:
        telnet.read_until(b"Username")
        telnet.write(to_bytes(username))
        telnet.read_until(b"Password")
        telnet.write(to_bytes(password))
        index, m, output = telnet.expect([b">", b"#"])
        if index == 0:
            telnet.write(b"enable\n")
            telnet.read_until(b"Password")
            telnet.write(to_bytes(enable))
            telnet.read_until(b"#", timeout=5)
        telnet.write(b"terminal length 0\n")
        telnet.read_until(b"#", timeout=5)
        time.sleep(3)
        telnet.read_very_eager()

        result = {}
        for command in commands:
            telnet.write(to_bytes(command))
            output = telnet.read_until(b"#", timeout=5).decode("utf-8")
            result[command] = output.replace("\r\n", "\n")
        return result


if __name__ == "__main__":
    devices = ["1.1.1.1"]
    commands = ["sh ip int br"]
    for ip in devices:
        result = send_show_command(ip, "username", "password", "", commands)
        pprint(result, width=120)

This code return login error (napalm library):

from napalm import get_network_driver
from pprint import pprint
  
driver = get_network_driver('ios')
conn_method = {'port': 23, 'transport': 'telnet', 'global_delay_factor': 2, 'secret': ''}
host = '1.1.1.1'
user = 'username'
passwd = 'password'
  

with driver(hostname=host, username=user, password=passwd, optional_args=conn_method ) as device:
    print('Getting facts')
    pprint(device.get_facts())

This code return login error (netmiko library):

import os
from netmiko import ConnectHandler

switch = {
    'device_type': 'cisco_ios_telnet',
    'ip': '1.1.1.1',
    "username": "username",
    "password": "password",
    "timeout": 15

}

net_connect = ConnectHandler(**switch)
print(net_connect)

Solution

  • As you have already mentioned NAPALM uses telnetlib for telnet connections. But it uses it with the help of netmiko.

    Regarding your question, the issue could be raised due to some factors:

    1. Connection timeout
    2. Authentication timeout

    Try to add conn_timeout: 30 and auth_timeout: 30 in conn_method variable (30 seconds is an example) to give time for your application to connect and authenticate to the network device.

    Also a recommendation to set fast_cli to False (default value is True). fast_cli simply multiplies the delay factor by 0.1. So if you have a delay factor of 1 (100 seconds), it means the application has 10 seconds only to screen scrape what is happening on the remote network device which is not always enough in some cases. This behavior is only in the case of using CiscoBaseConnection.

    For BaseConnection, fast_cli default value is False.

    Here is a complete working demo of your case

    from pprint import pprint
    
    from napalm import get_network_driver
    
    driver = get_network_driver("ios")
    
    creds = {
        "hostname": "192.168.1.150",
        "username": "cisco",
        "password": "cisco",
    }
    
    optionals = {
        "transport": "telnet",  # no need for port 23. It implicilty knows what port to set
        "secret": "", 
        "conn_timeout": 30,
        "auth_timeout": 30,
        "fast_cli": False,  # no need for global_delay_factor now in case of get_facts() only
    }
    
    
    with driver(**creds, optional_args=optionals) as device:
        print("Port:", device.device.port)  # prints 23
        print("Parsing facts...")
        facts = device.get_facts()
    pprint(facts)