home / 2018.11.04 10:30 / wake on lan / powershell / java / python
For those lazy Sunday mornings, when getting our of bed to wake up your media server seems excessive, but your laptop is within reach and writing a small program to send a Wake-On-LAN signal sounds so reasonable; it is in recognition of those mornings that I've put together this instructional page.
The resource I started off explains how to create and send a magic packet using powershell. That page contains all the information you need to create a magic packet that will wake your computer. I am adding the code below, in case that site goes down.
$Mac = "12:34:56:78:9A:BC"
$MacByteArray = $Mac -split "[:-]" | ForEach-Object { [Byte] "0x$_"}
[Byte[]] $MagicPacket = (,0xFF * 6) + ($MacByteArray * 16)
$UdpClient = New-Object System.Net.Sockets.UdpClient
$UdpClient.Connect(([System.Net.IPAddress]::Broadcast),7)
$UdpClient.Send($MagicPacket,$MagicPacket.Length)
$UdpClient.Close()
To summarize, if you want to wake your computer over the local network, you need to:
255
, followed by 16 repetitions of the bytes that form the MAC address you want to send this packet toFirst implementation will be in Java. We'll be using some utility functions for arrays and collections, and the java.net
package:
import java.net.*;
import java.util.Arrays;
import java.util.Collections;
public class Wol {
Next, we need a simple method to create the magic packet, based on the physical MAC address we want to wake. One strange limitation of Java is that the byte
primitive type is always signed, which means we can't actually store the value 0xFF
into it (compilation error). That value is considered too large to be stored into a byte, and the Java compilar will consider the 0xFF
literal an integer, so we always need to cast that literal to a byte
primitive. The rest of the method is pretty straight-forward: 6 bytes of value 255
, followed by 16 repetitions of the MAC address.
private static byte[] createMagicPacket(String physicalAddress) {
byte[] mac = macBytes(physicalAddress);
byte[] packet = new byte[6 + 16*6];
for (int i = 0; i < 6; i++) {
packet[i] = (byte) 0xFF;
}
for (int i = 0; i < 16; i++) {
System.arraycopy(mac, 0, packet, (i + 1) * 6 + 0, 6);
}
return packet;
}
Once we have the magic packet, we need to obtain the broadcast address for our LAN. The way I did this was to list all network interfaces, and find the one which has an address corresponding to the localhost address. Then I can get the broadcast address from that interface. Just in case this does not work, I've created a method to return the fallback address 255.255.255.255
.
private static InetAddress getBroadcastAddress() {
try {
InetAddress localHost = Inet4Address.getLocalHost();
return Collections.list(NetworkInterface.getNetworkInterfaces()).stream()
.flatMap(i -> i.getInterfaceAddresses().stream())
.filter(a -> Arrays.toString(a.getAddress().getAddress()).equals(Arrays.toString(localHost.getAddress())))
.map(a -> a.getBroadcast())
.findFirst().orElse(getFallbackBroadcastAddress());
} catch (Throwable t) {
return getFallbackBroadcastAddress();
}
}
private static InetAddress getFallbackBroadcastAddress() {
try {
return InetAddress.getByAddress(new byte[]{0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF});
} catch (UnknownHostException e) {
return null;
}
}
Now that we have gathered all the information we required, we can send the magick packet on its merry way. We create a datagram socket, on port 7
. This depends on the settings on your network card, usually network cards are listening to ports 0
, 7
or 9
. Often you can even configure those ports. My card works just great with the magic number 7
, so that's what I use here. We also need to configure our socket to broadcast, just a flag, then create and send the packet to the LAN broadcast address.
private static void sendBroadcast(InetAddress address, byte[] magicPacket) {
try {
int port = 7;
DatagramSocket socket = new DatagramSocket(port);
DatagramPacket packet = new DatagramPacket(magicPacket, magicPacket.length, address, port);
socket.setBroadcast(true);
socket.send(packet);
} catch (Throwable e) {
e.printStackTrace();
}
}
As a bonus, I've also added two methods, one to print a byte array as hexadecimal values, another to parse a string representing the MAC address into a byte array.
private static void printHexa(byte[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.printf("%02X ", arr[i]);
}
System.out.println();
}
private static byte[] macBytes(String mac) {
String[] tokens = mac.split("[.:-]");
if (tokens.length == 6) {
byte[] result = new byte[6];
for (int i = 0; i < 6; i++) {
result[i] = (byte) (Integer.parseInt(tokens[i],16) & 0xff);
}
return result;
} else {
return null;
}
}
And the main
method, putting it all together and firing up our program:
public static void main(String[] args) {
String mac = "12-34-56-78-9A-BC";
byte[] magicPacket = createMagicPacket(mac);
printHexa(magicPacket);
InetAddress broadcastAddress = getBroadcastAddress();
System.out.println(broadcastAddress);
sendBroadcast(broadcastAddress, magicPacket);
}
}
Now, we've got all this in place and working in Java, but maybe you don't want to fire up a JVM every time you're too lazy to get off the couch to wake your media server from sleep. You're too lazy to even start a JVM, so a Python utility may be a better solution for you. Jocularly enough, the Python program really is a lot less verbose than the Java counterpart.
The tool will use some basic python packages, but nothing that needs to be grabbed through pip:
import sys
import socket
import ipaddress
Same steps here as for the Java program, first we build our magic packet as an array of bytes. The parsing of the MAC string is included in this method, and concatenating the MAC address bytes is a lot easier here, just using the +=
operator. There's also no nonsense need for casts to byte.
def create_magic_packet(mac):
mac_string = ''.join(mac.split('-'))
mac_bytes = bytes.fromhex(mac_string)
magic_packet = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
for i in range(16):
magic_packet += mac_bytes
return magic_packet
Obtaining the broadcast address, without additional dependencies, seems a little harder. I've looked at several methods and the one below worked for me, but results may vary. The focus here is to find the local network card that is actually used to connect to the interned. We do that by opening a connection the Google's recursive name server, then we query the socket to obtain our local IP address. Once we have that we can create an IP interface object and get the broadcast address out of that. The only problem is that we also need to know what the network mask is, and I did not find a way to get that, so now I'm making assumptions that it's /24
. 255.255.255.255
is still used as the fallback address.
def get_broadcast_address():
try:
network_mask_length = 24
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
current_ip = s.getsockname()[0]
s.close()
return str(ipaddress\
.ip_interface(current_ip + "/" + str(network_mask_length))\
.network\
.broadcast_address)
except:
return '255.255.255.255'
We send the magic packet to the broadcast address by opening a socket and configuring it to work in broadcast mode.
def send_broadcast(packet, address):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.sendto(bytes(packet), (address, 7))
And the code that puts all this together; this program expects the MAC address as the first argument:
magic_packet = create_magic_packet(sys.argv[1])
print(magic_packet)
broadcast_address = get_broadcast_address()
print(broadcast_address)
send_broadcast(magic_packet, broadcast_address)
An interesting next step would be to make this a Python package that can be installed through pip. Maybe even add in a small database, so the tool can remember the MAC addresses you used in the past and allow you to resend the wake signal to them.