0

I'm having a hard time trying to make asyncio working with telnetlib for interrogate some hardware.

I think I clearly don't understand the way asyncio is working and I'm a completely lost in all of this. It's really unclear.

My basic version (which is synchrone) is working well but interrogating the complete list of equipments takes 6 hours actually and a large part of the equipements are not responding because they are unreachable.

Since Asyncio make us able to parallelize the connections without waiting for each timeout to trigger I would like to transform my code in proper asynchrone code, without success.

Here is what I tried :

import telnetlib
import time
import datetime
import asyncio

from env.fonctions import *
from env.variables import *


first_cmds = ['term length 0', \
              'show run', \
              'exit']

#create lists to iterate through
hosts = ['router-1', 'router-2', 'router-3', 'router-4', 'router-5']

async def main(hosts, user_rw_hw ,password_rw_hw, first_cmds):
    class ContinueI(Exception):
        pass

    continue_i = ContinueI()

    for host in hosts:
        print(f'{host} | Trying to connect...')
        try:
            tn = await async_establish_telnet_connexion(user_rw_hw ,password_rw_hw, host, 23, 0.5, True)
        except:
            continue

        print(f'{host} | Checking if equipment is not Nexus')

        tn.write('show version'.encode('ascii') + b"\n")
        sh_ver = await async_read_telnet_output(tn)
        if 'Nexus' in sh_ver or 'NX-OS' in sh_ver or 'nexus' in sh_ver:
            print(f'{host} | Equipment is Nexus, closing connection...')
            tn.write('exit'.encode('ascii') + b"\n")
            continue

        tn.write(''.encode('ascii') + b"\n")

        try:
            for cmd in first_cmds:
                tn.write(cmd.encode('ascii') + b"\n")
                if not 'exit' in cmd:
                    response = await async_read_telnet_output(tn)
                    if '\r\n% Invalid' in response:
                        print(f'{host} | Commande "{cmd}" pas reconnue')
                        raise continue_i
                else:
                    print(f'{host} | Commands are accepted')
        except ContinueI:
            tn.write(b"exit\n")
            tn.write(b"exit\n")
            print(f'{host} | Logout for command not recognized')
            continue


if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.set_debug(1)
        loop.run_until_complete(main(hosts, user_rw_hw ,password_rw_hw, first_cmds))

    except Exception as e:
        pass

    finally:
        loop.close()

and functions :

async def async_read_telnet_output(tn, timeout=2, timestep=0.1):
   timer = 0
   data = b''
   while timer <= timeout:
       new_datas = tn.read_very_eager()
       if len(new_datas) != 0:
           timer = 0
           data += new_datas
       await asyncio.wait(timestep)
       timer += timestep
   return data.decode('utf-8')


async def async_establish_telnet_connexion(user_rw_hw, password_rw_hw, host, port=23, timeout=1, debug=False):
   try:
       tn = telnetlib.Telnet(host, port)    # Here I don't know how to make it awaitable, if I put await before the IDE said that this method is not an awaitable, btw even if I put an awaitable like "asyncio.sleep" the behavior is still the same so it's not the only point bad
   except:
       if debug == True:
           print(f"{host} | Telnet not responding.")
       raise Exception

   if debug == True:
       print(f"{host} | Telnet is responding.")

   response = loop.create_task(async_read_telnet_output(tn, 15))

   if not 'Username:' in response and not 'login' in response:
       if debug == True:
           print(f"{host} | Don't see Username asked by equipment.")
       raise Exception
   else:
       tn.write(user_rw_hw.encode('ascii') + b"\n")

   if debug == True:
       print(f"{host} | Username entered.")

   try:
       await tn.read_until(b"Password: ", timeout)
   except:
       if debug == True:
           print(f"{host} | Don't see Password asked by equipment.")
       raise Exception
   finally:
       tn.write(password_rw_hw.encode('ascii') + b"\n")
       response = await async_read_telnet_output(tn, 10)
       if '% Authentication failed' in response or 'Rejected' in response:
           if debug == True:
               print(f"{host} | Connection failed bad credentials.")
           raise Exception
       if debug == True:
           print(f"{host} | Connection succeed waiting for commands.")
       return tn

If some people know where I fail I would be grateful i'm stuck since one week on it... Reading some books and youtube tutos but nothing help me..

Thank you by advance !

gotzilla
  • 1
  • 2
  • While it won't actually solve your problem, you might want to read up on [How does asyncio actually work?](https://stackoverflow.com/questions/49005651/how-does-asyncio-actually-work). Code has to *be* asynchronous, you cannot just ``await`` a synchronous function and expect it to become concurrent. Using threads instead of coroutines might be much simpler to solve your issue. – MisterMiyagi Nov 12 '20 at 18:14
  • Ok I will read it right away thank you MisterMiyagi :) – gotzilla Nov 12 '20 at 18:19
  • 1
    I understand your point now @MisterMiyagi, I was thinking that since telnet is waiting for answers sometimes it should automatically be "asynchronousable", but now I understand what you mean, I will take a look closer at this point. In fact I think it could be possible, since my function "async_read_telnet_output" is using a asyncio.sleep method which is awaitable if I understand well, then I will see to refactor my code, if it is the telnetlib that is not compatible, I could maybe take a look if the socket lib is "asynchronousable" and create a new telnet client from scratch. Thanks again ! – gotzilla Nov 13 '20 at 01:36

1 Answers1

0

For the ones landing here... I found https://pypi.org/project/asynctelnet/

quick exmple:

client:

import anyio, asynctelnet

async def shell(tcp):
    async with asynctelnet.TelnetClient(tcp, client=True) as stream:
        while True:
            # read stream until '?' mark is found
            outp = await stream.receive(1024)
            if not outp:
                # End of File
                break
            elif '?' in outp:
                # reply all questions with 'y'.
                await stream.send('y')

            # display all server output
            print(outp, flush=True)

    # EOF
    print()

async def main():
    async with await connect_tcp('localhost', 56023) as client:
        await shell(client)
anyio.run(main)

Server:

import anyio, asynctelnet

async def shell(tcp):
    async with asynctelnet.TelnetServer(tcp) as stream:
        # this will fail if no charset has been negotiated
        await stream.send('\r\nWould you like to play a game? ')
        inp = await reader.receive(1)
        if inp:
            await stream.echo(inp)
            await stream.send('\r\nThey say the only way to win '
                              'is to not play at all.\r\n')

async def main():
    listener = await anyio.create_tcp_listener(local_port=56023)
    await listener.serve(shell)
anyio.run(main)

Still this lib has some bugs. so the current state is "be prepared to code some bug workarounds if you want to use it".

NicoKowe
  • 2,989
  • 2
  • 19
  • 26