Goodies
Goodies

Reputation: 4681

Python3.5 Asyncio TCP Scanner

I am new to Python3.5, and Async in general with the exception of minor use of Twisted in Python 2.7. I'm building this into a larger application and I need a small portion, as opposed to a monolithic framework, to perform TCP port scanning.

import asyncio
from random import SystemRandom

def run(task, *, loop=None):
    if loop is None:
        loop = asyncio.get_event_loop()
    return loop.run_until_complete(asyncio.ensure_future(task, loop=loop))

async def scanner(ip, port, loop=None):
    fut = asyncio.open_connection(ip, port, loop=loop)
    try:
        reader, writer = await asyncio.wait_for(fut, timeout=0.5) # This is where it is blocking?
        print("{}:{} Connected".format(ip, port))
    except asyncio.TimeoutError:
        pass

def scan(ips, ports, randomize=False):
    if randomize:
        rdev = SystemRandom()
        ips = rdev.shuffle(ips)
        ports = rdev.shuffle(ports)
    for port in ports:
        for ips in ips:
            run(scanner(ip, port))

 ips = ["192.168.0.{}".format(i) for i in range(1, 255)]
 ports = [22, 80, 443, 8080]
 scan(ips, ports)

This is still taking as long as a single thread would take. How can I turn this into an async TCP scanner?

Upvotes: 2

Views: 2242

Answers (1)

kwarunek
kwarunek

Reputation: 12587

run_until_complete is blocking, the execution stops there and waits until the one scan ends, then the next one...

You should schedule all (or some part) of tasks and wait for all of them with wait.

import asyncio
from random import SystemRandom

def run(tasks, *, loop=None):
    if loop is None:
        loop = asyncio.get_event_loop()
    # waiting for all tasks
    return loop.run_until_complete(asyncio.wait(tasks))

async def scanner(ip, port, loop=None):
    fut = asyncio.open_connection(ip, port, loop=loop)
    try:
        reader, writer = await asyncio.wait_for(fut, timeout=0.5) # This is where it is blocking?
        print("{}:{} Connected".format(ip, port))
    except asyncio.TimeoutError:
        pass
    # handle connection refused and bunch of others
    except Exception as exc:
        print('Error {}:{} {}'.format(ip, port, exc))

def scan(ips, ports, randomize=False):
    loop = asyncio.get_event_loop()
    if randomize:
        rdev = SystemRandom()
        ips = rdev.shuffle(ips)
        ports = rdev.shuffle(ports)

    # let's pass list of task, not only one
    run([scanner(ip, port) for port in ports for ip in ips])

ips = ["192.168.0.{}".format(i) for i in range(1, 255)]
ports = [22, 80, 443, 8080]
scan(ips, ports)

I've also added except block to catch rest of exceptions, including most common connection refused.

Upvotes: 3

Related Questions