#!/usr/bin/env python3 """ Small module for use with the wake on lan protocol. """ import argparse import ipaddress import socket import typing BROADCAST_IP = '255.255.255.255' DEFAULT_PORT = 9 def create_magic_packet(macaddress: str) -> bytes: """ Create a magic packet. A magic packet is a packet that can be used with the for wake on lan protocol to wake up a computer. The packet is constructed from the mac address given as a parameter. Args: macaddress: the mac address or a "mac address/secureon password" tuple that should be parsed into a magic packet. """ secureon = '' if '/' in macaddress: (macaddress, secureon) = macaddress.split('/') if len(macaddress) == 17: sep = macaddress[2] macaddress = macaddress.replace(sep, '') elif len(macaddress) == 14: sep = macaddress[4] macaddress = macaddress.replace(sep, '') if len(macaddress) != 12: raise ValueError('Incorrect MAC address format') if secureon: if len(secureon) == 17: sep = secureon[2] secureon = secureon.replace(sep, '') elif len(secureon) == 14: sep = secureon[4] secureon = secureon.replace(sep, '') if len(secureon) != 12: raise ValueError('Incorrect SecureOn password format') return bytes.fromhex('F' * 12 + macaddress * 16 + secureon) def send_magic_packet( *macs: str, ip_address: str = BROADCAST_IP, port: int = DEFAULT_PORT, interface: typing.Optional[str] = None, address_family: typing.Optional[socket.AddressFamily] = None, ) -> None: """ Wake up computers having any of the given mac addresses. Wake on lan must be enabled on the host device. Args: macs: One or more mac addresses or "mac address/secureon password" tuples of machines to wake. Keyword Args: ip_address: the ip address of the host to send the magic packet to. port: the port of the host to send the magic packet to. interface: the ip address of the network adapter to route the magic packet through. address_family: the address family of the ip address to initiate connection with. When not specificied, chosen automatically between IPv4 and IPv6. """ packets = [create_magic_packet(mac) for mac in macs] if address_family is None: address_family = ( socket.AF_INET6 if _is_ipv6_address(ip_address) else socket.AF_INET ) with socket.socket(address_family, socket.SOCK_DGRAM) as sock: if interface is not None: sock.bind((interface, 0)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.connect((ip_address, port)) for packet in packets: sock.send(packet) def _is_ipv6_address(ip_address: str) -> bool: try: return isinstance(ipaddress.ip_address(ip_address), ipaddress.IPv6Address) except ValueError: return False def main(argv: typing.Optional[typing.List[str]] = None) -> None: """ Run wake on lan as a CLI application. """ parser = argparse.ArgumentParser( description='Wake one or more computers using the wake on lan protocol.', formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( 'macs', metavar='mac address', nargs='+', help='The mac addresses or "mac address/secureon password" tuples of the computers you are trying to wake.', ) parser.add_argument( '-6', '--ipv6', action='store_true', help='To indicate if ipv6 should be used by default instead of ipv4.', ) parser.add_argument( '-i', '--ip', default=BROADCAST_IP, help='The ip address of the host to send the magic packet to.', ) parser.add_argument( '-p', '--port', type=int, default=DEFAULT_PORT, help='The port of the host to send the magic packet to.', ) parser.add_argument( '-n', '--interface', help='The ip address of the network adapter to route the magic packet through.', ) args = parser.parse_args(argv) send_magic_packet( *args.macs, ip_address=args.ip, port=args.port, interface=args.interface, address_family=socket.AF_INET6 if args.ipv6 else None, ) if __name__ == '__main__': # pragma: nocover main()