Check If Computer On Network Is Asleep Without Waking It Up (Python)

50 views Asked by At

I want a quick way to check if a computer on the LAN is awake or not given its IP address (on Windows) without waking it up. I wrote the following, which works:

def is_awake(ipOrName, timeout=0.05):
  try:
    tt = round(timeout*1000)
    cmdargs = ["ping", "-n", "1", "-w", f'{tt}', "-a", ipOrName]
    result = subprocess.run(cmdargs, capture_output=True, text=True)
    if "Received = 1" in result.stdout:
      return True
    else:
      return False
  except Exception as e:
    print(f"Error checking IP reachability: {e}")
    return False

But I noticed that this could be quite slow when running it to sequentially check a lot of IP addresses (30, for example). I can add print statements to the code and see a visible delay before and after the subprocess.run() line for the computers that are turned off (it's essentially instant for the IPs corresponding to computers that are turned on). It almost seems like the timeout parameter isn't being respected, or maybe there's some other reason the subprocess.run() function is taking so long to return. I'm not sure.

Is there any better way to do this? The following is much faster, but I can't use it because it wakes the computer from sleep:

def is_reachable(ipOrName, port = 445, timeout=0.05):
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(timeout)
  try:
    s.connect((ipOrName, int(port)))
    s.shutdown(socket.SHUT_RDWR)
    return True
  except:
    return False
  finally:
    s.close()

Any ideas?

This video demonstrates how slow my first method is: https://www.youtube.com/shorts/SZSMOucKiuQ

1

There are 1 answers

0
tdelaney On

The video shows that a single failed ping completes in about the expected time, but since each ping is done serially, the aggregate time is relatively long. "Relatively" because this is perfectly acceptable in many situations.

You could parallelize these pings. Your current function only handles a single ping, so I had to make up another function that processes many. In the first case, you could use a thread pool

import multiprocessing.pool.ThreadPool as ThreadPool
import functools

def is_awake(ipOrName, timeout=0.05):
  try:
    tt = round(timeout*1000)
    cmdargs = ["ping", "-n", "1", "-w", f'{tt}', "-a", ipOrName]
    result = subprocess.run(cmdargs, capture_output=True, text=True)
    if "Received = 1" in result.stdout:
      return True
    else:
      return False
  except Exception as e:
    print(f"Error checking IP reachability: {e}")
    return False
    
def many_are_awake(ipOrNameList, timeout=0.05):
    worker = functools.partial(is_awake, timeout=timeout)
    with ThreadPool(len(ipOrNameList)) as pool:
        result = pool.map(worker, ipOrNameList)
    return result

But since ping.exe is a separate process already, you could use Popen instead of run, which doesn't wait for one ping to complete before starting the next.

def many_are_awake(ipOrNameList, timeout=0.05):
  try:
    tt = round(timeout*1000)
    procs = []
    for ipOrName in ipOrNameList:
      cmdargs = ["ping", "-n", "1", "-w", f'{tt}', "-a", ipOrName]
      proc = subprocess.Popen(cmdargs, stdout=subprocess.PIPE,
          stderr=subprocess.STDOUT))
      procs.append(proc)
    results = []
    for proc in procs:
      stdout, stderr = proc.communicate()
      results.append("Received = 1" in stdout)
    return results
  except Exception as e:
    print(f"Error checking IP reachability: {e}")
    return []

As a side note, a single ping with a short timeout will sometimes give you false negatives. Several pings with at a least a few second timeout would catch more. If running in parallel speeds things up enough, you may consider more attempts.