マシンを遠隔起動出来るWake-on-LAN(WoL)はとても便利です。
通常はWoL専用のツールを用いますが、そういうものをインストール出来ない、したくないといった状況でも、結構簡単にbashだけで自作することができます。
#!/bin/bash
readonly MAC=01:23:45:67:89:AB
readonly BROADCAST=255.255.255.255
readonly PORT=9
(
# FFを6回、改行なしで出力
for a in {1..6}; do
echo -en '\xFF'
done
# MACアドレスのコロンを取り除きバイナリ化したものを16回
for a in {1..16}; do
echo -en '\x'${MAC//:/\\x}
done
) | nc -b -w1 -u $BROADCAST $PORT
WoLの解説
まずWoLの仕組みを解説します。
WoLでマシンを起動するには、「マジックパケット」を同じネットワークにあるマシンからブロードキャスト1します。
マジックパケットについてはWikipediaの説明がわかりやすいので引用します。
マジックパケットは、FF:FF:FF:FF:FF:FFに続けて起動したい装置のMACアドレスを16回繰り返したデータパターン(AMD Magic Packet Format) がペイロードのどこかに含まれているようなパケットである。
-- 「Wake-on-LAN」(2019年3月7日 04:16 (UTC)の版)『ウィキペディア日本語版』。
このデータをUDPで2ブロードキャスト1すれば、該当マシンが起動してくれます。なおポート番号は何番でも良いです(パケットの中身しか見ていないので)が、慣例として9番が指定されることが多いです。
#シェルスクリプトの解説
WoLの仕組みを踏まえて、上記シェルスクリプトの中身を解説します。
まず、最初に定義されている変数については以下の通りです。
-
MAC
起動したいマシンのMACアドレスを:
区切りで指定します。ここはスクリプト内変数じゃなくてコマンドライン引数で実装するのも良い考えだと思います。 -
BROADCAST
WoLの送信先のブロードキャストアドレス。通常は255.255.255.255
を指定しておけば良いですが、送信先ネットワークを指定したい場合などは変更して下さい。 -
PORT
ポート番号。一応変数にしていますが、何番でも良いです。
{1..6}
は1 2 3 4 5 6
に展開されます。
$ echo {1..6}
1 2 3 4 5 6
for文に渡すことで、6回コマンドを繰り返すことが出来ます。
echo
コマンドの-n
オプションは改行を出力しないとうオプションです。-e
オプションはバックスラッシュによるエスケープを有効にします。WoLはバイナリデータなので必要です。
${MAC//:/\\x}
はMAC
という変数の:
を\x
に置換した文字列を表します。
$ MAC=01:23:45:67:89:AB
$ echo '\x'${MAC//:/\\x}
\x01\x23\x45\x67\x89\xAB
それを-en
オプションを付けることで、バイナリで出力することになります。
なお、nc
コマンドの-b
オプションは、必要な環境と不要な環境があります。例えばcygwinはその一つです。必要に応じて外して下さい。
またこのシェルスクリプトはzshでもそのまま実行できます。zshの場合、echo
コマンドはエスケープがオプションなしで有効になっており、-e
は単純に無視されるので、問題なく動きます。アンチGNUの人はzshで実行すると良いでしょう。
もともとこのシェルスクリプトを作ろうと考えたときは、POSIX互換で作るつもりだったんですが、
-
echo
コマンドは互換性に難がある -
printf
コマンドでバイナリを出力する場合、POSIX互換なら8進数指定が必要で面倒
ということが分かり、諦めました。3
#ncコマンド
シェルだけといいつつ、ncコマンドは使っています、、、
実はbashにはTCPやUDP通信を行う機能があります。bashのmanページを見てみましょう。
Bash handles several filenames specially when they are used in
redirections, as described in the following table. If the operating
system on which Bash is running provides these special files, bash will
use them; otherwise it will emulate them internally with the behavior
described below.
(略)
'/dev/udp/HOST/PORT'
If HOST is a valid hostname or Internet address, and PORT is an
integer port number or service name, Bash attempts to open the
corresponding UDP socket.
つまりbash上で/dev/udp/HOST/PORT
ファイルに書き込むとUDP通信が行われます。これを使えばncコマンドが不要になり、本当にbashのみでWoLが出来る、と期待したのですが、うまく行きませんでした。どうやらブロードキャストは出来ないようです。
-
ターゲットマシンにマジックパケットが届けば、同じネットワークでなくてもいいし、ブロードキャストでなくてもいいですが、起動していないマシンにはIPアドレスがないので(簡単には)パケットを送ることができません。 ↩ ↩2
-
ペイロードの中に含まれていればいいだけなので、別にUDPじゃなくてping(ICMP echo)でも生IPでも生ethernetフレームでも良いのですが、UDPが通常一番簡単です。 ↩
-
参考:どの環境でも使えるシェルスクリプトを書くためのメモ ver4.60のechoコマンドとprintf(コマンドおよびAWKの中の関数) ↩