Skip to content

Instantly share code, notes, and snippets.

@raghur
Created June 7, 2017 07:20
Show Gist options
  • Select an option

  • Save raghur/ba8da5eb9383814325a119cbc9744586 to your computer and use it in GitHub Desktop.

Select an option

Save raghur/ba8da5eb9383814325a119cbc9744586 to your computer and use it in GitHub Desktop.
powerwake script
#! /usr/bin/python
#
# powerwake - a smart remote host waking utility, supporting multiple
# waking methods, and caching known hostnames and addresses
#
# Copyright (C) 2009 Canonical Ltd.
#
# Authors: Dustin Kirkland <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import commands
import os
import re
import socket
import struct
import sys
import getopt
global DEBUG, PKG, RC, HOME
DEBUG = 0
PKG = "powerwake"
RC = 0
HOME = os.getenv("HOME")
short_opts = 'hb:m:'
long_opts = ['help', 'broadcast=', 'method=']
usage_string = "Usage:\n powerwake [-b|--broadcast BROADCAST_IP] [-m|--method METHOD] TARGET_MAC|TARGET_IP|TARGET_HOST"
# Generic debug function
def debug(level, msg):
if DEBUG >= level:
print("%s" % msg)
# Generic error function
def error(msg):
debug(0, "ERROR: %s" % msg)
ERROR = 1
# Generic warning function
def warning(msg):
debug(0, "WARNING: %s" % msg)
# Generic info function
def info(msg):
debug(0, "INFO: %s" % msg)
def wakeonlan(mac):
nonhex = re.compile('[^0-9a-fA-F]')
mac = nonhex.sub('', mac)
if len(mac) != 12:
error("Malformed mac address [%s]" % mac)
info("Sending magic packet to: [%s]" % mac)
# We should cache this to /var/cache/powerwake/ethers,
# in case arp entry isn't available next time
data = ''.join(['FFFFFFFFFFFF', mac * 16])
send_data = ''
for i in range(0, len(data), 2):
send_data = ''.join([send_data, struct.pack('B', int(data[i: i + 2], 16))])
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(send_data, (broadcast, 7))
except:
error("Network is unreachable")
def is_ip(ip):
r1 = re.compile('^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')
if r1.match(ip):
return 1
else:
return 0
def is_mac(mac):
r1 = re.compile('^[0-9a-fA-F]{12}$')
r2 = re.compile('^[0-9a-fA-F]{2}.[0-9a-fA-F]{2}.[0-9a-fA-F]{2}.[0-9a-fA-F]{2}.[0-9a-fA-F]{2}.[0-9a-fA-F]{2}$')
if r1.match(mac) or r2.match(mac):
return 1
else:
return 0
# Source the cached, known arp entries
def get_arp_cache():
host_to_mac = {}
for file in ["/var/cache/%s/ethers" % PKG, "/etc/ethers", "%s/.cache/ethers" % HOME]:
if os.path.exists(file):
f = open(file, 'r')
for i in f.readlines():
try:
(m, h) = i.split()
host_to_mac[h] = m
except:
pass
f.close()
return host_to_mac
# Source the current, working arp table
def get_arp_current(host_to_mac):
# Load hostnames
for i in os.popen("/usr/sbin/arp"):
m = i.split()[2]
h = i.split()[0]
if is_mac(m):
host_to_mac[h] = m
# Load ip addresses
for i in os.popen("/usr/sbin/arp -n"):
m = i.split()[2]
h = i.split()[0]
if is_mac(m):
host_to_mac[h] = m
return host_to_mac
def write_arp_cache(host_to_mac):
if not os.access("%s/.cache/" % HOME, os.W_OK):
return
if not os.path.exists("%s/.cache/" % HOME):
os.makedirs("%s/.cache/" % HOME)
f = open("%s/.cache/ethers" % HOME, 'a')
f.close()
for file in ["/var/cache/%s/ethers" % PKG, "%s/.cache/ethers" % HOME]:
if os.access(file, os.W_OK):
f = open(file, 'w')
for h in host_to_mac:
if is_mac(host_to_mac[h]):
f.write("%s %s\n" % (host_to_mac[h], h))
f.close()
def get_arp_hash():
host_to_mac = get_arp_cache()
host_to_mac = get_arp_current(host_to_mac)
write_arp_cache(host_to_mac)
return host_to_mac
# TODO:
# Parameters to add:
# --list - list known mac/ip-host combinations
# --clear - clear the cache
if __name__ == '__main__':
# set up defaults
broadcast = '<broadcast>'
method = 'wol'
# parse getopt options
try:
opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
except:
print(usage_string)
exit(1)
for name, value in opts:
if name in ('-b', '--broadcast'):
if is_ip(value):
broadcast = value
else:
error("Value passed to " + name + " is not a valid broadcast address [%s]" % value)
print(usage_string)
exit(1)
elif name in ('-m', '--method'):
method = value
elif name in ('-h', '--help'):
print(usage_string)
exit(0)
# if args is less than one, we don't have a host to wake up
if len(args) < 1:
error("Please provide one or more MAC addresses, IP addresses, or Hostnames")
print(usage_string)
exit(1)
# Process command line parameters
for i in args:
# If parameter matches a system with a static configuration, use it!
# ^^^ Not yet implemented, default to wake-on-lan method for now
if method == "wol":
if is_mac(i):
# this appears to be a mac address, just use it
m = i
pass
else:
# Retrieve the mac cache
host_to_mac = get_arp_cache()
if host_to_mac.has_key(i):
# mac found in the hash
info("Trying to wake host: [%s]" % i)
m = host_to_mac[i]
else:
# not in the cache, do the expensive arp search
host_to_mac = get_arp_hash()
if host_to_mac.has_key(i):
# mac found in the hash
info("Trying to wake host: [%s]" % i)
m = host_to_mac[i]
else:
# cannot autodetect the mac address
error("Could not determine the MAC address of [%s]" % i)
continue
if is_mac(m):
wakeonlan(m)
else:
error("Could not wake host [%s]" % m)
else:
error("Unsupported method [%s]" % method)
RC=1
sys.exit(RC)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment