HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonModerate

Python Port Scanner

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
scannerpythonport

Problem

This is only my third Python script. Be brutal with me. Any tips, tricks, best practices, or better usages would be great!

import socket
from concurrent.futures import ThreadPoolExecutor

THREADS = 512
CONNECTION_TIMEOUT = 1

def ping(host, port, results = None):
    try:
        socket.socket().connect((host, port))
        if results is not None:
            results.append(port)
        print(str(port) + " Open")
        return True
    except:
        return False

def scan_ports(host):
    available_ports = []
    socket.setdefaulttimeout(CONNECTION_TIMEOUT)
    with ThreadPoolExecutor(max_workers = THREADS) as executor:
        print("\nScanning ports on " + host + " ...")
        for port in range(1, 65535):
            executor.submit(ping, host, port, available_ports)
    print("\nDone.")
    available_ports.sort()
    print(str(len(available_ports)) + " ports available.")
    print(available_ports)

def main():
    scan_ports("127.0.0.1")

if __name__ == "__main__":
    main()

Solution

It's not Pythonic to pass in a mutable object and use that to store results (like it is in C).

range(start, stop) is not inclusive of stop, so you have an off by one error as well.

Using a catch-all except line is also poor practice. It's important to catch expected exceptions and reraise the rest.

Also Python lists are thread safe only because of the GIL in CPython. This code would not be thread safe in other implementations such as Jython. Notice how your version always reports open ports in ascending order.

Threads are fine for IO bounded actions, but since pinging localhost is so fast GIL contention slows down performance in this case. Your implementation ran in 27 seconds on my laptop.

By comparison, my implementation ran in 2.76 seconds. Replacing map() with pool.map() led to a runtime of 1.38 seconds:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from errno import ECONNREFUSED
from functools import partial
from multiprocessing import Pool
import socket

NUM_CORES = 4

def ping(host, port):
    try:
        socket.socket().connect((host, port))
        print(str(port) + " Open")
        return port
    except socket.error as err:
        if err.errno == ECONNREFUSED:
            return False
        raise

def scan_ports(host):
    p = Pool(NUM_CORES)
    ping_host = partial(ping, host)
    return filter(bool, p.map(ping_host, range(1, 65536)))

def main(host=None):
    if host is None:
        host = "127.0.0.1"

    print("\nScanning ports on " + host + " ...")
    ports = list(scan_ports(host))
    print("\nDone.")

    print(str(len(ports)) + " ports available.")
    print(ports)

if __name__ == "__main__":
    main()

Code Snippets

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from errno import ECONNREFUSED
from functools import partial
from multiprocessing import Pool
import socket

NUM_CORES = 4


def ping(host, port):
    try:
        socket.socket().connect((host, port))
        print(str(port) + " Open")
        return port
    except socket.error as err:
        if err.errno == ECONNREFUSED:
            return False
        raise


def scan_ports(host):
    p = Pool(NUM_CORES)
    ping_host = partial(ping, host)
    return filter(bool, p.map(ping_host, range(1, 65536)))


def main(host=None):
    if host is None:
        host = "127.0.0.1"

    print("\nScanning ports on " + host + " ...")
    ports = list(scan_ports(host))
    print("\nDone.")

    print(str(len(ports)) + " ports available.")
    print(ports)


if __name__ == "__main__":
    main()

Context

StackExchange Code Review Q#38452, answer score: 11

Revisions (0)

No revisions yet.