Created
June 7, 2017 07:20
-
-
Save raghur/ba8da5eb9383814325a119cbc9744586 to your computer and use it in GitHub Desktop.
powerwake script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /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