|
|
@@ -0,0 +1,145 @@ |
|
|
#!python |
|
|
|
|
|
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 corputine 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:]))) |