Skip to content

Instantly share code, notes, and snippets.

@wowkin2
Forked from 0xpizza/async_tcp_scan.py
Created December 26, 2018 08:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wowkin2/aa54f2be2e89a885cd7fcf7c33d5b22b to your computer and use it in GitHub Desktop.
Save wowkin2/aa54f2be2e89a885cd7fcf7c33d5b22b to your computer and use it in GitHub Desktop.
TCP Network scanner using asyncio for Python 3.7
#!/usr/bin/python3.7
import asyncio
import ipaddress
import re
import sys
MAX_NUMBER_WORKERS = 200
def ipsort(t):
'''used to sort output by ipaddress, then port'''
return tuple([*map(int, t[0].split('.')), t[1]])
def eprint(*args, **kwargs):
print(*args, **kwargs, file=sys.stderr)
def parseports(portstring):
'''
syntax: port,port-range,...
use regex to verify input validity, then create a tuple of
ports used in port scan. there definitely some room for optimization
here, but it won't matter much. go optimize the coroutines instead.
'''
if not re.match(r'[\d\-,\s]+', portstring):
raise ValueError('Invalid port string')
ports = []
portstring = list(filter(None, portstring.split(',')))
for port in portstring:
if '-' in port:
try:
port = [int(p) for p in port.split('-')]
except ValueError:
raise ValueError('Are you trying to scan a negative port?')
for p in range(port[0], port[1]+1):
ports.append(p)
else:
ports.append(int(port))
for port in ports:
if not (-1 < port < 65536):
raise ValueError('Ports must be between 0 and 65535')
return tuple(set(ports))
def fancy_print(data, csv):
if csv:
fmt = '{},{}'
else:
fmt = '{:<15} :{}'
for datum in data:
print(fmt.format(*datum))
async def task_worker(task_queue, out_queue):
'''pull connection information from queue and attempt connection'''
while True:
ip, port, timeout = (await task_queue.get())
conn = asyncio.open_connection(ip, port)
try:
await asyncio.wait_for(conn, timeout)
except asyncio.TimeoutError:
pass
else:
out_queue.put_nowait((ip, port))
finally:
task_queue.task_done()
async def task_master(
network: str, portrange: str, timeout: float,
task_queue: asyncio.Queue, scan_completed: asyncio.Event):
'''add jobs to a queue, up to ``MAX_NUMBER_WORKERS'' at a time'''
network = network.replace('/32', '')
try:
# check to see if we are scanning a single host...
hosts = [str(ipaddress.ip_address(network)),]
except ValueError:
# ...or a CIDR subnet.
hosts = map(str, ipaddress.ip_network(network).hosts())
for ip in hosts:
for port in portrange:
await task_queue.put((ip, port, timeout))
scan_completed.set()
async def main(network, ports=None, timeout=0.1, csv=False):
'''
main task coroutine which manages all the other functions
if scanning over the internet, you might want to set the timeout
to around 1 second, depending on internet speed.
'''
task_queue = asyncio.Queue(maxsize=MAX_NUMBER_WORKERS)
out_queue = asyncio.Queue()
scan_completed = asyncio.Event()
scan_completed.clear() # progress the main loop
if ports is None: # list of common-ass ports
ports = ("9,20-23,25,37,41,42,53,67-70,79-82,88,101,102,107,109-111,"
"113,115,117-119,123,135,137-139,143,152,153,156,158,161,162,170,179,"
"194,201,209,213,218,220,259,264,311,318,323,383,366,369,371,384,387,"
"389,401,411,427,443-445,464,465,500,512,512,513,513-515,517,518,520,"
"513,524,525,530,531,532,533,540,542,543,544,546,547,548,550,554,556,"
"560,561,563,587,591,593,604,631,636,639,646,647,648,652,654,665,666,"
"674,691,692,695,698,699,700,701,702,706,711,712,720,749,750,782,829,"
"860,873,901,902,911,981,989,990,991,992,993,995,8080,2222,4444,1234,"
"12345,54321,2020,2121,2525,65535,666,1337,31337,8181,6969")
ports = parseports(ports)
# initialize task to add scan info to task queue
tasks = [asyncio.create_task(
task_master(network, ports, timeout, task_queue, scan_completed)
)]
# initialize workers
for _ in range(MAX_NUMBER_WORKERS):
tasks.append(asyncio.create_task(task_worker(task_queue, out_queue)))
eprint('scanning . . .')
await scan_completed.wait() # wait until the task master coro is done
await task_queue.join() # wait for workers to finish
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
eprint('gathering output . . .')
openports = []
while out_queue.qsize():
openports.append(out_queue.get_nowait())
openports.sort(key=ipsort)
fancy_print(openports, csv=csv)
eprint('shutting down . . .')
if __name__ == '__main__':
# import argparse?
if len(sys.argv) < 2:
print(
'TCP Network scanner using asyncio module for Python 3.7+',
"Scan ports in ``portstring'' or common ports if blank."
'Port string syntax: port, port-range ...',
f'Usage: {sys.argv[0]} network [portstring]',
sep='\n'
)
raise SystemExit
elif len(sys.argv) == 2:
asyncio.run(main(sys.argv[1]))
else:
asyncio.run(main(sys.argv[1], ''.join(sys.argv[2:])))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment