🔌

Wake on LAN を使ったり作ったりしてみる

2021/09/25に公開

先週,私の部屋に新たな仲間が加わりました. 2004 年 10 月に発売された FUJITSU 製の FMV-C330 です. 17 年という時間のどこかで Windows XP を置いてきてしまったようなので,FreeBSD をインストールして頑張ってもらうことにしました.

そんなおんぼろですが,配線や物理的な空きスペースの都合もあって棚の一番奥,机から手を伸ばしてもぎりぎり届かない絶妙な位置に配置せざるを得ませんでした. これでは電源を投入するためにわざわざ椅子から立ち上がる手間が掛かります.

そこで私は思い出しました. 先程まで嫌になるほど睨み合った BIOS の設定画面にあった Wake on LAN の文字を……

Wake on LAN とは

読んで字の如くです. LAN からコンピュータの電源を投入する仕組みのことです. Wake on LAN (WOL) に対応しているコンピュータでは,コンピュータの電源が切れた後もネットワークカード (NIC) に電源が供給されます. NIC が自分自身宛ての起動要求 (Magic Packet) を受信すると,コンピュータ全体の電源を投入するように処理を実行します.

Magic Packet の中身

次に示すものは,MAC アドレス DE:AD:BE:EF:CA:FE を持つコンピュータ宛ての Magic Packet の一部を 16 進数で dump したものです. 全てのビットが 1 である 6 バイト (ff ff ff ff ff ff) に続けて,起動したいコンピュータの MAC アドレス (de ad be ef ca fe) を 16 回続けた,計 102 バイトのパターンであることがわかります[1]

000000 ff ff ff ff ff ff de ad be ef ca fe de ad be ef
000010 ca fe de ad be ef ca fe de ad be ef ca fe de ad
000020 be ef ca fe de ad be ef ca fe de ad be ef ca fe
000030 de ad be ef ca fe de ad be ef ca fe de ad be ef
000040 ca fe de ad be ef ca fe de ad be ef ca fe de ad
000050 be ef ca fe de ad be ef ca fe de ad be ef ca fe
000060 de ad be ef ca fe
000066

Magic Packet を投げてみる

実際に Magic Packet を投げてコンピュータに電源を投入してみましょう. Ubuntu から Magic Packet を投げるには wakeonlan(1) を利用します. インストールするには次のように実行します.

$ sudo apt update; sudo apt upgrade
$ sudo apt install wakeonlan

そして起動したいコンピュータの MAC アドレスを指定して次のように実行すると,うまくいけば遠隔でコンピュータの電源を投入できます. 電源が投入されないときは,対象のコンピュータが WOL に対応しているか,WOL で電源が投入されるような設定が構成されているか確認してください.

$ wakeonlan de:ad:be:ef:ca:fe

どうして MAC アドレスを指定するのか

wakeonlan(1) に渡したパラメータは IP アドレスではなく MAC アドレスでした. どうしてでしょうか. その理由は NIC そのものが IP を理解しないことにあります.

NIC は Ethernet フレームを理解します[2]. これにはパケットの宛先 MAC アドレスや送信元 MAC アドレスが含まれており,NIC はその情報によって自分自身に向けられたパケットであるか否かを判断します. 宛先 IP アドレス等を含む IP ヘッダは Ethernet にとってただのデータですから,それを利用してホストを特定することはできません.

自分で Magic Packet を投げてみる

wakeonlan(1) を使ってコンピュータの電源を投入することは誰でもできるので,Magic Packet を投げるプログラムをサクっと自作してみましょう. ここでは簡単のために UDP/IPv4 を利用したプログラムを作成しました. これならよくあるソケットプログラミングで間に合います.

プログラムソースコードは GitHub にもあります. このプログラムは LAN 上のホストの UDP 9 番ポートを目掛けて Magic Packet をブロードキャストします. TCP/UDP の 9 番ポートに割り当てられているサービスは Discard サービスで,これは受信したデータを全て破棄するものです. このポートを利用することで,仮に対象のコンピュータが起動済みであっても問題を引き起す恐れが低くなります.

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>

#define PORT 9

int main(int argc, char *argv[])
{
	int i, err, yes;
	char *ptr;
	char macaddr[6], magic[102];
	int sock;
	struct sockaddr_in addr;

	if (argc != 2) {
		fprintf(stderr, "Usage: %s macaddr\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	/* Building a Magic Packet */
	ptr = argv[1];
	for (i = 0; i < 6; i++) {
		macaddr[i] = (char)strtoul(ptr, &ptr, 16);
		ptr++;
	}
	for (i = 0; i < 6; i++)
		magic[i] = 0xff;
	for (i = 6; i < 102; i++)
		magic[i] = macaddr[i % 6];

	/* Preparing for packet dispatch */
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(EXIT_FAILURE);
	}
	yes = 1;
	err = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
	if (err == -1) {
		perror("setsockopt");
		exit(EXIT_FAILURE);
	}

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;			/* IPv4 */
	addr.sin_addr.s_addr = INADDR_BROADCAST;	/* Broadcast */
	addr.sin_port = htons(PORT);			/* discard service */

	/* dispatch packet */
	err = sendto(sock, magic, sizeof(magic), 0,
				(const struct sockaddr *)&addr, sizeof(addr));
	if (err == -1) {
		perror("sendto");
		exit(EXIT_FAILURE);
	}

	close(sock);

	return 0;
}

おわりに

おわりです. Wake on LAN の概念こそ知っていましたが,実際に使ったことがなかったので良い機会になりました.

参考

脚注
  1. 正確には “起動したいコンピュータに搭載されている NIC の MAC アドレス” ですが,誰も気にしないでしょう. ↩︎

  2. その NIC が Ethernet 用のものであれば. ↩︎

Discussion