diary

離れているPCの電源を入れる

without comments

我が家の音楽や映像などのマルチメディアデータは、ほとんどが、別の部屋にあるデスクトップ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();
  }

}

Written by Kei

December 25th, 2007 at 9:35 pm

Posted in Tech

Leave a Reply