diff --git a/test_wakeonlan.py b/test_wakeonlan.py index bebc0b4..235c775 100644 --- a/test_wakeonlan.py +++ b/test_wakeonlan.py @@ -293,6 +293,131 @@ def test_send_magic_packet_interface(sock: Mock) -> None: ] +@patch("socket.socket") +def test_send_correct_af_chosen_with_ipv6_address(sock: Mock) -> None: + """ + Test whether AF_INET6 automatically chosen when the `address_family` argument is not given. + """ + send_magic_packet( + "133713371337", + "00-00-00-00-00-00", + ip_address="fc00::", + port=7, + ) + assert sock.mock_calls == [ + call(socket.AF_INET6, socket.SOCK_DGRAM), + call().__enter__(), + call().__enter__().setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1), + call().__enter__().connect(("fc00::", 7)), + call() + .__enter__() + .send( + b"\xff\xff\xff\xff\xff\xff" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + ), + call() + .__enter__() + .send( + b"\xff\xff\xff\xff\xff\xff" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + ), + call().__exit__(None, None, None), + ] + + +@patch("socket.socket") +def test_send_with_explicit_ipv6_address(sock: Mock) -> None: + """ + Test whether the given address family is used instead automatically it automatically. + """ + send_magic_packet( + "133713371337", + "00-00-00-00-00-00", + ip_address="example.com", + port=7, + address_family=socket.AF_INET6, + ) + assert sock.mock_calls == [ + call(socket.AF_INET6, socket.SOCK_DGRAM), + call().__enter__(), + call().__enter__().setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1), + call().__enter__().connect(("example.com", 7)), + call() + .__enter__() + .send( + b"\xff\xff\xff\xff\xff\xff" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + b"\x137\x137\x137" + ), + call() + .__enter__() + .send( + b"\xff\xff\xff\xff\xff\xff" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00" + ), + call().__exit__(None, None, None), + ] + + @patch("wakeonlan.send_magic_packet") def test_main(send_magic_packet: Mock) -> None: """ @@ -301,12 +426,27 @@ def test_main(send_magic_packet: Mock) -> None: """ main(["00:11:22:33:44:55", "-i", "host.example", "-p", "1337"]) main(["00:11:22:33:44:55", "-i", "host.example", "-p", "1337", "-n", "192.168.0.2"]) + main(["00:11:22:33:44:55", "-i", "host.example", "-p", "1337", "-6"]) assert send_magic_packet.mock_calls == [ - call("00:11:22:33:44:55", ip_address="host.example", port=1337, interface=None), + call( + "00:11:22:33:44:55", + ip_address="host.example", + port=1337, + interface=None, + address_family=None, + ), call( "00:11:22:33:44:55", ip_address="host.example", port=1337, interface="192.168.0.2", + address_family=None, + ), + call( + "00:11:22:33:44:55", + ip_address="host.example", + port=1337, + interface=None, + address_family=socket.AF_INET6, ), ] diff --git a/wakeonlan/__init__.py b/wakeonlan/__init__.py index 0f5dcfe..ca91696 100755 --- a/wakeonlan/__init__.py +++ b/wakeonlan/__init__.py @@ -4,6 +4,7 @@ """ import argparse +import ipaddress import socket from typing import List from typing import Optional @@ -40,7 +41,8 @@ def send_magic_packet( *macs: str, ip_address: str = BROADCAST_IP, port: int = DEFAULT_PORT, - interface: Optional[str] = None + interface: Optional[str] = None, + address_family: Optional[socket.AddressFamily] = None ) -> None: """ Wake up computers having any of the given mac addresses. @@ -54,11 +56,18 @@ def send_magic_packet( 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] - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + 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) @@ -67,6 +76,13 @@ def send_magic_packet( 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: Optional[List[str]] = None) -> None: """ Run wake on lan as a CLI application. @@ -82,6 +98,12 @@ def main(argv: Optional[List[str]] = None) -> None: nargs="+", help="The mac addresses of the computers you are trying to wake.", ) + parser.add_argument( + "-6", + dest="use_ipv6", + action="store_true", + help="To indicate if ipv6 should be used by default instead of ipv4.", + ) parser.add_argument( "-i", metavar="ip", @@ -102,7 +124,13 @@ def main(argv: Optional[List[str]] = None) -> None: 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.i, port=args.p, interface=args.n) + send_magic_packet( + *args.macs, + ip_address=args.i, + port=args.p, + interface=args.n, + address_family=socket.AF_INET6 if args.use_ipv6 else None + ) if __name__ == "__main__": # pragma: nocover