離れているPCの電源を入れる
我が家の音楽や映像などのマルチメディアデータは、ほとんどが、別の部屋にあるデスクトップPC (通称: 母艦) に保存されています。母艦は普段は電源が落ちていて、別のPCからこのデータを見たいときに、電源を入れる、という使い方をします。我が家はそんなに大きな家ではないので、どこにいても、たかだか20歩も歩けば母艦にたどり着くのですが、それでもたまに、「電源を入れたり切ったりするために隣の部屋まで歩いていく」というのが、どうしようもうなくメンドクサかったりします。それくらい歩けよ! って感じなんですが。
電源の入っているPCを落とすのは、母艦のOSに Windows Remote Desktop なり VNC なりで繋いで、OSから電源を落とせばいいだけなのすが、問題は電源を入れるほう。電源が入ってないので OS につながるわけもなく、やはり物理的に、あの電源ボタンを押す以外に方法はないんじゃないか? と思っていたのですが…
世の中には同じような不精者が他にもたくさんいるようで、なんと、ネットワーク越しに離れているPCの電源を入れる、という方法が、意外と簡単に見つかりました。Wake on LAN (WOL) というそうです。
読んでみたところ、最近の PC は大抵は対応しているとのこと。試しに、母艦のBIOS設定画面を見ると… Power Management のところに、「Remote Wakeup on S5」 という項目がありました。どうやら、なんとなくいけそうです。さっそく、試してみました。
@IT の日本語の記事にしたがって、まずは母艦の設定をします。この記事では、母艦の OS のほうにもいろいろ設定が必要そうなことが書いてありますが、たぶん、電源が入っちまえまばこっちのもんなんで、PC の BIOS の設定だけで動くと思います。BIOS の設定は PC によって大分違うと思います。ちなみに、ワタシの eMachines の PC では、この「Remote Wakeup on S5」 という項目を有効にしました。ちなみに、S5 は「電源が完全に落ちている」というステータスのことを意味するようです。詳しくは記事を参照してください。
次に、リモート側の設定。ワタシは、Magic Packet Sender というツールをインストールして使ってみました。ツールをインストールしたら、起動して、以下の情報を入力します。
- 母艦の IP アドレス
- 母艦の MAC アドレス
- サブネットマスク
入力が終わったら、Send ボタンを押します。すると、遠くのほうで「ブオーン」と PC のファンの回る音が聞こえました! どうやら、成功のようです。すげー簡単。
@ITの記事を読んでいると、どうやら、母艦を起こすための呪文 (magic packet) は、意外に簡単そうな感じ。
WOLの動作の仕組みは、次のようなものだ。WOL対象となるコンピュータ(ネットワーク・アダプタ)に対して特殊なパケットを送出する。すると、WOL 対象コンピュータのネットワーク・アダプタがパケットを受け取り、電源投入する。WOLのパケットにはいくつかの方式があるが、一般的なMagic Packet方式では、IPヘッダに続いて、0xffffffffffff(6bytes)と、WOL対象コンピュータのネットワーク・アダプタのMAC アドレス(6bytes)が16回連続する102bytesのデータを持つUDPデータグラムとなっており、宛先のUDPポートは任意である。
「用は、0xff が6回のあとに、MAC アドレス が16回繰り返えされたデータを UDP で送りつければいいんじゃね? ちょっとこの呪文を送るプログラム書いてみるか。Magic Packet Sender には、自分のマシンに呪文がやってきたらポップアップする、というサーバのような機能もあるから、試すのも簡単そうだし。」 と思い立って、ババッと10分くらいでプログラム書いてみました。ためしに、Magic Packet Sender の動いている自分のマシンに向かって実行してみたら、小気味よくポンとポップアップ。さすが俺様。しかし、なぜか、電源に消えている母艦にこの呪文を送っても、母艦は起動せず…
よく考えてみたら、母艦は落ちているんだから、IPアドレス指定したって届かないんじゃね? そういや、Magic Packet Sender では、サブネットマスクを入力する必要があったのを思い出して、あぁ、あれはきっと、母艦がいそうなあたりのネットワークにブロードキャストで送ってるんだな、と推測。Wireshark (Ethereal っていつの間にか名前変わったのね…)で Magic Packet Sender の挙動を調べてみたら、まさにその通りでした。ブロードキャストアドレスに送るようにプログラム書き換えて、再度、母艦に呪文を送ったら、遠くのほうで「ブオーン」。成功です。
自分が何やったか忘れちゃいそうなので、未来の自分のためにコード載せておきます。ひょっとしたら、誰かの役に立つかもしれないし。
import java.io.ByteArrayOutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.StringTokenizer; /** * Wake up caller. It sends the "wake-up-call" for the PC. * * Usage: Call this class from the command line. Syntax is following. * java WakeUpCaller [IP address] [Subnet mask] [mac address] * ex) java WakeUpCaller 192.168.1.5 255.255.255.0 11:11:11:11:11:11 * * @author Kei * */ public class WakeUpCaller { public static void main(String[] args) throws Exception { if (args.length != 3) { System.out.println("java WakeUpCaller [IP address] [Subnet mask] [mac address]"); System.out.println("ex) java WakeUpCaller 192.168.1.5 255.255.255.0 11:11:11:11:11:11"); return; } String hostname = args[0]; String subnet = args[1]; String macAddr = args[2]; // mac address byte[] mac = new byte[6]; // Parse mac address StringTokenizer st = new StringTokenizer(macAddr, " :-"); if (st.countTokens() != 6) { System.err.println("Invalid mac address"); return; } for (int i=0; i<6; i++) { mac[i] = (byte)Integer.parseInt(st.nextToken(), 16); } // Calculate broadcast address byte[] hostAddr = InetAddress.getByName(hostname).getAddress(); byte[] subnetAddr = InetAddress.getByName(subnet).getAddress(); byte [] addr = new byte[4]; for (int i=0; i<4; i++) { addr[i] = (byte)(hostAddr[i] | (~subnetAddr[i])); } InetAddress address = InetAddress.getByAddress(addr); // Create data ByteArrayOutputStream buf = new ByteArrayOutputStream(); // Add 6 bytes with 0xff. for (int i=0; i<6; i++) { buf.write((byte)0xff); } // Repeat target mac address 16 times. for (int i=0; i<16; i++) { buf.write(mac); } buf.flush(); buf.close(); byte[] data = buf.toByteArray(); // Create UDP packet. DatagramPacket packet = new DatagramPacket(data, data.length, address, 9); // Create UDP socket. DatagramSocket socket = new DatagramSocket(); // Send packet. socket.send(packet); // Close socket. socket.close(); } }