Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying ipv6 addresses #26

Merged
merged 1 commit into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 141 additions & 1 deletion test_wakeonlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand All @@ -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,
),
]
34 changes: 31 additions & 3 deletions wakeonlan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

"""
import argparse
import ipaddress
import socket
from typing import List
from typing import Optional
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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",
Expand All @@ -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
Expand Down