1

So yesterday I was practicing what I learnt over the past few days and decided to create a script to scan through all the IPs in the local network and check which ones are being used.

I used subprocess the use the "ping" command with a given timeout, and other few libraries such as docopt, threading and time for common tasks such as handling command line arguments, threading, waiting code etc...

Here's the script:

""" ipcheck.py - Getting available IPs in a network.


Usage:
    ipcheck.py -h | --help
    ipcheck.py PREFIX
    ipcheck.py [(-n <pack_num> PREFIX) | (-t <timeout> PREFIX)]

Options:
   -h --help        Show the program's usage.
   -n --packnum     Number of packets to be sent.
   -t --timeout     Timeout in miliseconds for the request. 
"""

import sys, os, time, threading
from threading import Thread
from threading import Event
import subprocess
import docopt


ips = [] # Global ping variable


def ping(ip, e, n=1, time_out=1000):

    global ips

    # FIX SO PLATFORM INDEPENDENT
    # Use subprocess to ping an IP
    try:
        dump_file = open('dump.txt', 'w')
        subprocess.check_call("ping -q -w%d -c%s %s" % (int(time_out), int(n), ip),
        shell=True, stdout=dump_file, stderr=dump_file)
    except subprocess.CalledProcessError as err:
        # Ip did not receive packets
        print("The IP [%s] is NOT AVAILABLE" % ip)
        return
    else:
        # Ip received packets, so available
        print("The IP [%s] is AVAILABLE" % ip)
        #ips.append(ip)
    finally:
        # File has to be closed anyway
        dump_file.close()

        # Also set the event as ping finishes
        e.set()
        ips.append(1)


def usage():
    print("Helped init")

def main(e):

    # variables needed
    timeout = 1000
    N_THREADS = 10


    # Get arguments for parsing
    arguments = docopt.docopt(__doc__)

    # Parse the arguments
    if arguments['--help'] or len(sys.argv[1:]) < 1:
        usage()
        sys.exit(0)
    elif arguments['--packnum']:
        n_packets = arguments['--packnum']
    elif arguments['--timeout']:
        timeout = arguments['--timeout']
    prefix = arguments['PREFIX']


    # Just an inner function to reuse in the main
    # loop.
    def create_thread(threads, ip, e):

        # Just code to crete a ping thread
        threads.append(Thread(target=ping, args=(ip, e)))
        threads[-1].setDaemon(True)
        threads[-1].start()
        return


    # Do the threading stuff
    threads = []

    # Loop to check all the IP's 
    for i in range(1, 256):
        if len(threads) < N_THREADS:

            # Creating and starting thread
            create_thread(threads, prefix+str(i), e)

        else:
            # Wait until a thread finishes
            e.wait()

            # Get rid of finished threads
            to_del = []
            for th in threads:
                if not th.is_alive(): to_del.append(th)
            for th in to_del: threads.remove(th)

            # Cheeky clear init + create thread
            create_thread(threads, prefix+str(i), e)
            e.clear()

    time.sleep(2*timeout/1000) # Last chance to wait for unfinished pings
    print("Program ended. Number of threads active: %d." % threading.active_count())

if __name__ == "__main__":
    ev = Event()
    main(ev)

The problem I'm having is that, although I'm setting a timeout (in milliseconds) for the ping command, some threads do not finish some reason. I fixed this temporarily by making all the threads daemonic and waiting twice the timeout after the program finishes (last few lines in main), but this doesn't work as expected, some threads are still not finished after the sleep.

Is this something to do with the command ping itself or is there a problem in my design ?

Peace!

Mat Gomes
  • 435
  • 1
  • 8
  • 22
  • Have you tried to let it do it in a shell (cmd) command? – Rootel Aug 04 '16 at 17:37
  • Yeah I ran the command on the shell and it never goes over the timeout. That's sort of why the -w command is there for, so it doesn't wait for a response for longer than that. – Mat Gomes Aug 04 '16 at 17:46
  • But in the code it still seems to go over that sometimes and it doesn't terminate. – Mat Gomes Aug 04 '16 at 17:46
  • Hmmm, i get it. I prefer to use Batch or C# for internet related functions. In a way your script isn't running smooth. You should debug it and let it write a TXT of what the script is doing. When it goes over your bug, you should see what is happening – Rootel Aug 04 '16 at 17:50
  • Are the target hosts specified by ip address or by name? I've checked the ping source, and it performs the DNS lookup before setting the deadline timer. So the ping command can run beyond the deadline if the DNS lookup is slow. – ErikR Aug 04 '16 at 19:15
  • Ah that makes sense! Maybe the dns lookups are taking forever sometimes. And the host is specified bu IP. – Mat Gomes Aug 04 '16 at 19:17
  • well - afaict, no DNS lookup is performed if you are using ip-address. Another option is to use [__fping__](http://fping.sourceforge.net) – ErikR Aug 04 '16 at 19:27
  • Oh man, this is confusing then... I have no clue why the request is going over the timeout then hahaha (in fact, over twice the timeout as seen in the code) – Mat Gomes Aug 05 '16 at 18:07

1 Answers1

0

Python 3.3 implements a timeout= keyword parameter to subprocess.check_call():

https://docs.python.org/3.3/library/subprocess.html#subprocess.check_call

Otherwise I would use another thread to ensure that the spawned command is killed after the timeout period - i.e. see this SO answer:

https://stackoverflow.com/a/6001858/866915

Community
  • 1
  • 1
ErikR
  • 51,541
  • 9
  • 73
  • 124