14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

KLab EngineerAdvent Calendar 2019

Day 7

全自動でWake On LANしたい

Last updated at Posted at 2019-12-07

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

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

動機とあらすじ

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

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

image.png

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

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

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

image.png

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

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

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

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

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

/etc/systemd/system/wol@.service
[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 の場合はソケット接続をacceptするたびに同名のサービステンプレート(この場合ならrdp-proxy@.service)が毎回呼び出されます。

/etc/systemd/system/rdp-proxy@.service
[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 で先ほど作ったマジックパケットを投げるサービスを指定しています。

メインの処理としてはncコマンドを使ってプロキシしています。既に説明したように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で待ち受けられます。

14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?