🦀

Wake on Lan のマジックパケットを送信するプログラムを Rust で実装した

2025/03/03に公開

Wake on Lan とは

Wake on Lan は、コンピュータネットワークに特定のパケットを送信させることにより、そのパケットの内容に該当するコンピュータが自ら電源を投入させる仕組みです(参考 : Wikipedia)

Wake on Lan を使用することで、遠隔でコンピュータの電源を入れることができるようになります。

送信する「特定のパケット」を マジックパケット といいます。
マジックパケットは FF:FF:FF:FF:FF:FF に続けて起動したい装置の MAC アドレスを16回繰り返したデータパターンのパケットです。
例えば、起動したい装置の MAC アドレスが EE:EE:EE:00:00:01 の場合、マジックパケットは以下のようになります。

FF:FF:FF:FF:FF:FF
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01
EE:EE:EE:00:00:01

マジックパケットをブロードキャストアドレスの UDPのポート9 に送ることで、指定した MAC アドレスのコンピュータの電源を入れることができます。
(電源を投入される PC が Wake on Lan に対応している必要があります)

プログラムの説明

今回は以下の処理を行う Rust のプログラムを作成しました。

  1. MAC アドレスを受け取る
  2. マジックパケットを生成
  3. UDP の 9 番ポートに送信

作成したプログラムは以下のリポジトリにあります。

https://github.com/shu-kitamura/wake-on-lan/tree/main

作成したプログラムの説明を記載します。

MAC アドレスを受け取る

MAC アドレスは clap を使用して、コマンドライン引数で受け取るようにしました。

Cargo.toml の dependencies に clap を指定します。
clap のバージョンはプログラム作成時(2025/03)の最新バージョンです。

[dependencies]
clap = { version = "4.5.31", features = ["derive"] }

ソースコードは以下のようになりました。

use clap::Parser;

// コマンドライン引数の定義
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
struct Args {
    /// MAC ADDRESS
    #[arg(value_name = "MAC_ADDRESS")]
    mac_address: String,
}

マジックパケットを生成

関数 parse_mac_address&str で受け取った MAC アドレスを 16 進数に変換します。
関数 create_magic_packetparse_mac_address を 16 回実行します。

// MAC アドレスを受け取り、その MAC アドレスに対応するマジックパケットを生成する
fn create_magic_packet(mac_address: &str) -> Result<Vec<u8>, ParseIntError> {
    // FF:FF:FF:FF:FF:FF という 6 バイトのヘッダを生成
    let mut magic_packet: Vec<u8> = vec![0xff; 6];

    // MAC アドレスを 16 回繰り返して追加する
    for _ in 0..16 {
        let parsed_address: Vec<u8> = parse_mac_address(mac_address)?;
        magic_packet.extend_from_slice(&parsed_address);
    }
    Ok(magic_packet)
}

// MAC アドレスを 16 進数のベクタに変換する
fn parse_mac_address(mac_address: &str) -> Result<Vec<u8>, ParseIntError> {
    let mut parsed: Vec<u8> = Vec::new();
    for octet in mac_address.split(":") {
        parsed.push(u8::from_str_radix(octet, 16)?);
    }
    Ok(parsed)
}

マジックパケットを UDP の ポート9に送信

以下の関数でマジックパケットを送信します。

// マジックパケットをブロードキャストアドレスの udp のポート9に送信する
fn send_magic_packet(magic_packet: Vec<u8>) -> Result<usize, std::io::Error> {
    let socket: UdpSocket = UdpSocket::bind("0.0.0.0:0")?;
    socket.set_broadcast(true)?;
    socket.send_to(&magic_packet, "255.255.255.255:9")
}

main 関数

上記の関数を順に実行します。

fn main() {
    // コマンドライン引数をパースし、MAC アドレスを取得する
    let args = Args::parse();
    let mac_address: String = args.mac_address;

    // マジックパケットを生成する
    let magic_packet: Vec<u8> = match create_magic_packet(&mac_address) {
        Ok(packet) => packet,
        Err(e) => { // エラーは表示して、プログラムを終了する
            eprintln!("Error: {}", e);
            return;
        }
    };
    
    // マジックパケットを送信する
    match send_magic_packet(magic_packet) {
        Ok(_) => println!("Magic Packet sent to {mac_address}"),
        Err(e) => eprintln!("Error: {}", e)
    }
}

以下のメッセージが出て、指定した MAC アドレスのコンピュータに電源が入れば成功です。

Magic Packet sent to <指定した MAC アドレス>

Discussion