Suspend on LAN

ネットワークに接続されたPCを遠隔で起動する技術にWake on LAN(WoL) があります。NIC、マザーボード、BIOS、OSなどが対応している必要がありますが、最近のハードウェアとOSなら大抵の場合利用できます。典型的な利用状況としては、同じネットワーク内に属する別の端末から起動したいPCのMACアドレスを含んだマジックパケット1をブロードキャスト送信して目的のPCの電源を投入という手順を踏みます。
遠隔からPCを起動させることのできるWoLですが、遠隔からPCの電源を落とす仕組みまでは決められていません。そこでこの記事では、WoLでPCを起動する場合と同じマジックパケットでPCを終了する仕組みを作ります。マジックパケットでPCのシャットダウンができれば同じインターフェイスで遠隔からのPCの起動と終了のどちらもできるようになります。
通常ではWoLにおいてターゲットのPCに送るマジックパケットはUDPパケットとして送られます。PCが起動している間にマジックパケットを受け取っても何も変化はありませんが、自分宛てのマジックパケットを監視してPCを終了するようにすることでSuspend on LANを実現できます。
今回は前の記事の内容を使ってWindowsサービスとして常駐するように作ってみました。自分のMACアドレスの取得にはnetifacesパッケージを使っています。
import win32serviceutil | |
import servicemanager | |
import ctypes | |
import socket | |
import select | |
import netifaces | |
import time | |
from contextlib import closing | |
def suspend(hibernate=False): | |
ctypes.windll.PowrProf.SetSuspendState(hibernate, 0, 0) | |
def macaddrs(): | |
return [netifaces.ifaddresses(iface_name)[netifaces.AF_LINK][0]['addr'].lower() for iface_name in netifaces.interfaces()] | |
def get_macaddr(data): | |
if data[:6] != '\xFF' * 6 or len(data) != 102: | |
return None | |
mac = data[6:12] | |
if data[6:] != mac * 16: | |
return None | |
return ':'.join(['%02x' % ord(x) for x in mac]) | |
class SoLService(win32serviceutil.ServiceFramework): | |
_svc_name_ = 'SoLService' | |
_svc_display_name_ = 'Suspend on LAN' | |
_svc_description_ = 'Suspend on LAN Service' | |
port = 9 | |
hibernate = True | |
def SvcStop(self): | |
self.run = False | |
def SvcDoRun(self): | |
self.run = True | |
with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as sock: | |
sock.bind(('', self.port)) | |
while self.run: | |
rlist, wlist, xlist = select.select([sock], [], [], 0) | |
for s in rlist: | |
try: | |
data, addr = s.recvfrom(1024) | |
if get_macaddr(data) in macaddrs(): | |
servicemanager.LogInfoMsg('Suspended by SoL') | |
suspend(self.hibernate) | |
except socket.error as e: | |
pass | |
else: | |
time.sleep(0.1) | |
if __name__ == '__main__': | |
win32serviceutil.HandleCommandLine(SoLService) |
この例では、UDPの9番ポートを監視して自分宛てのマジックパケットを受信したらPCをハイバネートします。ファイアウォールがある場合はUDPの9番ポートの受信を許可しておかないとPythonがマジックパケットを拾えないので注意してください。
Image based on SNIPICONS by Barseghyan / CC BY-SA
-
FF:FF:FF:FF:FF:FFに続けて起動したい装置のMACアドレスを16回繰り返したデータを含むパケット。 ↩︎