PHPでWake-on-LANしてみたり、wol代替コマンドにしてみたり。2009年07月23日 05時48分39秒

WoL事情を調べてみた。なんとなく。

社内でちょっとした必要があって、Web経由でWake-on-LANによるPCの起動をするツールでも作ろうかと思って調べてみた。

Vineなんかはapt経由でwolコマンド(どうやらこちらのコマンドがオリジナルみたい)をさくっとインストールできたり、CentOSやDebianにもコマンドがあるらしいことがわかった。

これならPHP経由でもコマンド叩くだけで必要な機能は実装できそう(といいつつ、CentOSのethtoolはroot権限必要だけど)なので楽かなーとか思っていたが、Windowsでデバッグするのにちょっと面倒くさそう。

じゃ、PHPだけでやってみるか

PHPだけでなんとかできんものかと思って調べたら、すでにやっておられる方もいるので、自分でもやってみることにした。

WakeOnLan.php

こんな感じでやってみた。仮に「WakeOnLan.php」とでもしておく。

<?php
class WakeOnLan {
    const BROADCAST_MAC_ADDR = 'FF:FF:FF:FF:FF:FF';
    
    const DEFAULT_BROADCAST_IP = '255.255.255.255';
    
    const DEFAULT_PORT = 2304;
    
    public static function macAddrToBytes($mac) {
        $mac = (string)$mac;
        if(! self::isValidMacAddr($mac)) {
            throw new Exception('invalid MAC address');
        }
        
        $buf = array();
        foreach(preg_split('/[:\-]/', $mac) as $one_octet) {
            $buf[] = chr(intval($one_octet, 16));
        }
        return join('', $buf);
    }
    
    public static function isValidMacAddr($mac) {
        return preg_match('/^[\da-zA-Z]{2}([:\-][\da-zA-Z]{2}){5}$/', $mac);
    }
    
    protected $_broadcastIp;
    
    protected $_port;
    
    public function __construct($broadcastIp = null, $port = null) {
        $this->setBroadcastIp($broadcastIp)->setPort($port);
    }
    
    public function getBroadcastIp() {
        return $this->_broadcastIp;
    }
    public function setBroadcastIp($ip) {
        $ip = (string)$ip;
        if(empty($ip)) $ip = self::DEFAULT_BROADCAST_IP;
        $this->_broadcastIp = $ip;
        return $this;
    }
    public function getBroadcastUrl() {
        return 'udp://' . $this->getBroadcastIp();
    }
    
    public function getPort() {
        return $this->_port;
    }
    public function setPort($port) {
        if($port == null) $port = -1;
        $port = (int)$port;
        if($port < 0) $port = self::DEFAULT_PORT;
        $this->_port = $port;
        return $this;
    }
    
    public function sendTo($mac) {
        $data = self::macAddrToBytes(self::BROADCAST_MAC_ADDR);
        $mac_data = self::macAddrToBytes($mac);
        for($i = 0; $i < 16; $i++) $data .= $mac_data;
        
        $fp = @fsockopen($this->getBroadcastUrl(), $this->getPort(), $errno, $errstr);
        if( $fp === false ) {
            throw new Exception($errstr . '(' . $errno . ')');
        }
        fwrite($fp, $data);
        @fclose($fp);
        return $this;
    }
}

ソース中に特にコメント入れてないけど、結構コンパクトなコードなので読むのはそんなに難しくないかと。WoL自体はWikipedia@itの記事を見ればだいたいわかるし。

んで、使い方はかなり単純で、

  • コンストラクタ(またはsetter)でブロードキャストアドレスとポートを設定し、
  • sendToメソッドにターゲットのMACアドレスを渡す
だけ。

たとえばMACアドレスが「00-0D-59-B5-31-08」なんてPCがあって、起動すると192.168.0.0/24なアドレスが割り振られるネットワークにいたとすると、これをWoLで起動する場合は

<php
require_once 'WakeOnLan.php';

$wol = new WakeOnLan('192.168.0.255', 2304);
$wol->sendTo('00-0D-59-B5-31-08');

てな感じで使う。あ、MACアドレスはハイフン区切りでもコロン区切りでも同じ動作をします。

ほんとはもうちょっと簡単

先の例ではご丁寧にブロードキャストアドレス/ポートとも指定したが、WoL発行元と同一セグメント上のPCを起こすなら「255.255.255.255」(=リミテッド・ブロードキャスト・アドレスっていうらしい)で問題ないし、ポートについても気休め程度のものらしいので、コンストラクタ引数を省略して

$wol = new WakeOnlan();
だけでもよい。

で、バッチで包む

例であげたような起動用PHPを作成してもいいんだけど、引数をそのまま流し込めばいいくらいなので、こんなバッチファイルで済ませられる。

@echo off
php -r "require_once 'WakeOnLan.php'; $wol=new WakeOnLan('%2'); $wol->sendTo('%1');"

これを「wol.bat」とか「wol.cmd」とかって名前でパスが通った場所に保存して(WakeOnLan.phpはinclude_pathが設定されているところにおいておけばいいでしょ)、
wol 00:0D:59:B5:31:08
とか、
wol 00:0D:59:B5:31:08 192.168.0.255
なんて感じで叩いたり、1行 - 1MACアドレスなテキストファイル作って、
for %m in ('type macaddr.txt') do @wol %m
みたいな感じで一斉起動とか。

余談

WakeOnLan.phpではMagicPacketを送出するのにfsockopen() → fwrite()だけで済ませてるけど、参考にさせてもらったこちらのコードではsocket_set_option()でブロードキャスト許可設定をしている。fsockopen()の場合にはいらないんだろうか。それともデフォルトでブロードキャスト許可されてるんだろうか。