IPv6 usage is confusing
At an ipv6-only host I get only that
rpi:~ # wakeonlan -6 1C:6F:65:C9:87:46
Traceback (most recent call last):
File "/root/.local/bin/wakeonlan", line 8, in <module>
sys.exit(main())
^^^^^^
File "/root/.local/share/pipx/venvs/wakeonlan/lib64/python3.11/site-packages/wakeonlan/__init__.py", line 127, in main
send_magic_packet(
File "/root/.local/share/pipx/venvs/wakeonlan/lib64/python3.11/site-packages/wakeonlan/__init__.py", line 74, in send_magic_packet
sock.connect((ip_address, port))
OSError: address family mismatched
rpi:~ #
without -6 option it also doesn't work:
rpi:~ # wakeonlan 1C:6F:65:C9:87:46
Traceback (most recent call last):
File "/root/.local/bin/wakeonlan", line 8, in <module>
sys.exit(main())
^^^^^^
File "/root/.local/share/pipx/venvs/wakeonlan/lib64/python3.11/site-packages/wakeonlan/__init__.py", line 127, in main
send_magic_packet(
File "/root/.local/share/pipx/venvs/wakeonlan/lib64/python3.11/site-packages/wakeonlan/__init__.py", line 74, in send_magic_packet
sock.connect((ip_address, port))
OSError: [Errno 101] Network is unreachable
rpi:~ #
Whether to use ipv6, is detected automatically from the ip address given. It’s also possible to pass a domain name, e.g. localhost or google.com. If a domain is passed, the ip version can’t be used automatically. The -6 flag is to support domain names that use ipv6. If the network is unreachable, you can try explicitly specifying the interface to use using the -n flag.
Pinging @alimg for verification, because they implemented this feature in #26.
usage: wakeonlan [-h] [-6] [-i ip] [-p port] [-n interface] mac address [mac address ...]
./wakeonlan -n eth0 -6 -i 2001:a61:483:c901:1e6f:65ff:fec9:8746
wakeonlan: error: the following arguments are required: mac address
./wakeonlan -n eth0 -6 fusion.tschaeferm.dynv6.net
or
./wakeonlan -n eth0 -6 1C:6F:65:C9:87:46
dont work
You write a tool as not planned to work. That is crazy.
@thomasschaeferm, the error message you got in your first command clearly states what was missing. You must always provide a mac address along with the ip address. The -i option is not a substitute of mac address, so your command line should look like ./wakeonlan -n eth0 -6 -i <ip-or-hostname> <mac-address>
The limitation @remcohaszing mentioned about automatic ipv6 detection with domains is true. I should clarify although its not explicitly documented in the CLI help message, the -i option actually accepts an hostname in place of an ip address.
Please also note that the CLI defaults to broadcast ip (255.255.255.255) when the -i option is omitted. This is not a valid ipv6 address, so if your interface does not have an ipv4 there is nothing that the tool can actually do. There is no such thing as broadcast ip in ipv6 networks. If you are looking for waking up more than one machine in your network, I suppose your best bet is to figure out a multicast address that works for you.
So the -i is optional for IPv4 and if omitted defaults to IPv4 broadcast (255.255.255.255), but is mandatory in IPv6? Which then probably leads to the issue here where the CLI help indicates the parameter as optional, but in some cases it is actually mandatory?
Why not use the all nodes multicast for IPv6 in order to mirror the IPv4 behaviour? The interface is then already supplied in that case, so it should be able to construct ff02::1%<interface> for the all nodes multicast out that interface?
other tools
./pywake 1C:6F:65:C9:87:46
sending IPv6 packet to IP ff02::1 for MAC 1C:6F:65:C9:87:46
@thomasschaeferm wrote:
You write a tool as not planned to work. That is crazy.
Be respectful. This is an OSS project that’s created and maintained by volunteers, so you get to use it for free.
My takeaway from further discussion is that this project does work as intented, but it has some confusing behaviour. I’m thinking of the following solution:
- Rename the
ipargument tohostname, with the-oand--hostnameCLI flags. (-halready means help.) - Change the
--interfaceshort flag from-nto-i, since-iis no longer taken. - Change the default
hostnamefrom255.255.255.255toNone. - If
hostnameis an IPv4 address, ignore theaddress_familyoption. Always use IPv4. - If
hostnameis an IPv6 address, ignore theaddress_familyoption. Always use IPv6. - If
hostnameisNoneandaddress_familyisAF_INET6, useff02::1as the hostname. - If
hostnameisNoneandaddress_familyis notAF_INET6, use255.255.255.255as the hostname. - If
hostnamesomething else, respectaddress_familyas-is, default toAF_INET. - Maybe add some logging.
These are breaking changes, so this requires a semver major release.
Any thoughts?
@remcohaszing I have a suggestion.
I have encountered an issue when WOL does not work (with defaults) where the domain resolves to IPv6. This happens because by default the address_family is AF_INET. I need to manually check what the domain resolves to, which is an inconvenience.
What if you allow hostname to have 3 values:
wakeonlan.IPV4_BROADCASTwakeonlan.IPV6_BROADCASTip_address- a normal string value
Then pass the hostname to socket.getaddrinfo(hostname, DEFAULT_PORT, family=socket.AF_UNSPEC) to get a list of all addresses. For example, localhost returns this:
print(socket.getaddrinfo("localhost", 9, family=socket.AF_UNSPEC))
# [(<AddressFamily.AF_INET6: 23>, 0, 0, '', ('::1', 9, 0, 0)), (<AddressFamily.AF_INET: 2>, 0, 0, '', ('127.0.0.1', 9))]
And then, if user has specified address_family, you take only the matching families from the list and use their addresses. Otherwise, if user did not specify anything, send WOL to all the addresses in the list.
This way you don't need to know anything about the hostname by default, but still can limit to v4 or v6 interface if desired.
This is what I'm currently using:
from wakeonlan import send_magic_packet
def wake_on_lan(address: str, mac: str):
import socket
import ipaddress
default_port = 9
def _try_parse_info(ip_address: str) -> tuple[socket.AddressFamily, str]:
try:
parsed_address = ipaddress.ip_address(ip_address)
if isinstance(parsed_address, ipaddress.IPv4Address):
return socket.AF_INET, ip_address
if isinstance(parsed_address, ipaddress.IPv6Address):
return socket.AF_INET6, ip_address
except ValueError:
pass
return None
# if valid IP was specified, we don't need to call `getaddrinfo` at all
info = _try_parse_info(address)
if info:
infos = [info]
else:
infos = [(x[0], x[4][0]) for x in socket.getaddrinfo(address, default_port, family=socket.AF_UNSPEC)
if x[0] in [socket.AF_INET, socket.AF_INET6]]
if not infos:
raise Exception("WOL failed - {address} does not have any IPv4 or IPv6 interfaces!")
for family, address_info in infos:
send_magic_packet(mac, ip_address=address_info, address_family=family, port=default_port)
def main():
address = "[::1]"
wake_on_lan(address, "00-B0-D0-63-C2-26")
main()