全自動でWake On LANしたい


本稿はKLab Engineer Advent Calendar 2019の7日目の記事です。デバッグに手間取っていたら遅刻しました…。

systemdとTCPプロキシとWake On LANの話題です。

動機とあらすじ

私はMacユーザーなのですが、自宅のWindowsマシンをリモートデスクトップ経由で使うことがあります。

このWindowsマシンは普段スリープ状態にしているので、使いたいときはまずWake On LANのマジックパケットを投げて起こしてやる必要があります1

しかし、Windows機のMACアドレスを忘れていたり、そもそもマジックパケットを投げるコマンドを忘れていたりして手間取ることが珍しくありません。

もちろん以前使ったコマンドが履歴に残っているので何とかなるのですが、そもそもマジックパケットを毎回手動で投げるのが面倒に思えてきました。

そこで、systemdでTCPプロキシを構成し、さらにプロキシ接続前に自動的にマジックパケットを投げるような仕組みを作ってみました。我が家には常時起動しているRaspberry Piがあるので、ここでTCPプロキシを運用することにしました。

これにより手元のMacでマジックパケットを投げる一手間が不要になり、リモートデスクトップのGUIから接続先を選ぶだけでWindowsにログオンできるようになります。

本稿ではこの仕組みの詳細を紹介します。プロトコルに依存した部分はないのでリモートデスクトップ以外でも利用できるはずです。

systemdからマジックパケットを投げる

systemdは最近のLinuxで採用されている高機能なサービス管理プログラムです。

まずはマジックパケットを投げる処理を紹介します。これはoneshotのサービステンプレートで記述しました。

/etc/systemd/system/[email protected]
[Unit]
Description = Sending Wake on LAN packet (oneshot)
After = network-online.target
Wants = network-online.target

[Service]
Type = oneshot
ExecStart = /usr/sbin/etherwake %i

サービステンプレートというのはサービス名の一部(インスタンス文字列)を可変文字列にしてサービスに引き渡すような仕組みです。ここではインスタンス文字列としてMACアドレスを指定して使っています。

$ sudo systemctl start wol@00:00:5e:00:53:01.service

こうするとMACアドレス 00:00:5e:00:53:01 宛にマジックパケットが送られます。

TCPプロキシで接続前にマジックパケットを投げる

次にsystemdでTCPプロキシを作っていきます。こちらは2ファイル構成です。

/etc/systemd/system/rdp-proxy.socket
[Unit]
Description = Socket for RDP (Remote Desktop Protocol) proxy

[Socket]
ListenStream = 0.0.0.0:3389
Accept = yes

[Install]
WantedBy = sockets.target

上記ファイルを設置した上で自動起動を有効にします。

$ sudo systemctl enable rdp-proxy.socket
$ sudo systemctl start rdp-proxy.socket

ListenStream=の設定により、TCPの3389番で待ち受けてサービスに受け渡します2。また、Accept = yes の場合は同名のサービステンプレートを作成する必要があります。

/etc/systemd/system/[email protected]
[Unit]
Description = Server for RDP (Remote Desktop Protocol) Proxy
Requires = wol@00:00:5e:00:53:01.service
After = network-online.target wol@00:00:5e:00:53:01.service
Wants = network-online.target

[Service]
Type = simple
ExecStartPre = /usr/bin/timeout 60 /bin/sh -c 'until /bin/nc -w 5 -z 192.0.2.1 3389; do sleep 1; done'
ExecStart = /bin/nc -q 10 192.0.2.1 3389
StandardInput = socket
StandardOutput = socket
StandardError = journal

Requires で先ほど作ったマジックパケットを投げるサービスを指定しています。

メインの処理としてはnetcatを使ってプロキシしています。1コネクションごとに1プロセスが起動することになります。

ただし、単にnetcatでプロキシするだけでは期待通りに動きませんでした。というのも、マジックパケットを投げてからリモートデスクトップサーバがlisten状態になるまでタイムラグがあるので、しばらく待ってからでないと接続できないのです。そこで接続前にExecStartPreを使ってTCP接続できるかどうかのチェックをして、接続できるようになってからプロキシするようにしました。

IPアドレスとMACアドレスは例示用ですので、上記設定を利用する際は適宜変更してご利用ください。

実際に使ってみて

実際に使ってみると予想以上に実用的でした。プロキシに使っているのはRaspberry Pi 2ですが、性能面の問題はなさそうです(実験した限りではCPU負荷やネットワーク帯域など余裕があったため)。

ちなみに私の環境だとプロキシに接続してからスリープしていたWindowsにリモートデスクトップ接続するまで約30秒のタイムラグがあります。耐えられないほどではないのですが、遅すぎる気がするので何か改善ポイントがあるのかもしれません。

まとめ

systemdでTCPプロキシを作り、その接続前に任意の外部コマンドが実行できることを示しました。今回は外部コマンドでWake On LANのマジックパケットを投げましたが、AWSインスタンスを立ち上げてから接続するなどの応用もできそうです。

それにしてもsystemdって何でもできますね…。

参考URL


  1. リモートデスクトップ専用機で普段は使わないため手の届かないところに置いてあるのです 

  2. 他にもListenDatagram=を使えばUDPで待ち受けられます。