面向虚拟机Wake-on-LAN的Windows服务开发
Tsccai

为什么是虚拟机WoL?

我有一台HP MicroServer Gen8服务器,操作系统是Windows Server 2012R2。上面跑了一个VirtualBox的CentOS虚拟机。有的时候我需要通过WoL启动宿主服务器,再启动虚拟机。但我并不是每次启动服务器后都立即启动虚拟机,所以最佳的方式是启动一个服务来监听WoL报文,一旦有匹配上虚拟机MAC的WoL报文达到,就启动虚拟机(通常是采用命令行在后台启动虚拟机,没有窗口界面)。但这个需求似乎很小众,GitHub上这类项目不多,且都是8~10年以上的老项目,还都没有实际代码。另外有一个Wake-On-LAN Virtual Machine,功能完美匹配我的需求,但我从来没有成功启动过,而且这还是个收费软件。

没办法,只能自己造轮子了。但普通的Windows服务开发我打算略过,我也不过是看了几篇博客和微软的官方文档。这里重点记录一下开发过程中的遇到的一些坑。

Windows服务使用的账号

首先,因为我使用的是Virtualbox,所以服务的ProcessInstaller中必须将Account属性设为User。否则服务运行在LOCAL_SYSTEM或者LOCAL_SERVICE账号下,这些账号里是没有Virtualbox和相应的虚拟机的
ProcessInstaller中的Account属性须设为User
在部署服务时,需要填入安装Virtualbox所使用的账号和密码,即确保服务和Virtualbox都在同一个账号下。而填入的这个账号格式与通常我们登录用的还不一样,具体可在终端里通过命令whoami来确定(使用微软账号时也有效)。
服务安装时输入账号

SharpPcap抓包过滤器

SharpPcap的过滤器写法同WireShark,要抓取WoL的Magic Packet,过滤器应为:

1
device.Filter = "ether dst FF:FF:FF:FF:FF:FF and udp dst port 9";

SharpPcap异常退出

我在部署到服务器后发现,服务只能执行1次启动虚拟机的操作,执行后服务就停止了,虽然打了log,写了系统日志,都没有捕捉到任何异常,十分的诡异。于是,为了防止内部抛出异常把整个服务给拖死,在服务启动后,抓包及后续启动虚拟机的任务不能放在服务的主线程中进行,必须另起一个线程来执行,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// WoLService.cs
public partial class VMWoLService : ServiceBase
{
Thread captureThread;
ICaptureDevice device;
protected override void OnStart(string[] args) {
try {
// 初始化操作,主要包括日志初始化
InitResource();

captureThread = new Thread(InitCaptureDevice);
captureThread.Start();

tw.WriteLine($"{DateTime.Now}: 服务已启动");
tw.Flush();
}
catch (Exception e) {
tw.Write(e.StackTrace);
tw.Flush();
EventLog.WriteEntry(e.StackTrace);
throw e;
}
}
private void InitCaptureDevice() {
/*
TODO: 1. 初始化SharpPcap,指定要抓包的网卡
2. 绑定OnPacketArrival事件,用于启动虚拟机
3. 绑定OnCaptureStopped事件,用于打印日志,查找异常
*/
}
}

好了,现在服务不会那么轻易挂掉了,但通过日志发现,在启动虚拟机后,抓包的线程就死掉了,抓包也中止了,所以后续无法再响应WoL报文。就这个问题我在开发环境和生产环境进行了4次测试,终于大致确定了问题所在。先罗列一下开发和生产环境的软件配置

环境 操作系统 Virtualbox版本
开发环境 Windows 10 Professional 7.08
生产环境 Windows Server 2012R2 6.1

测试方法及结果

编号 测试方法 结果
1 开发环境中部署服务,多次发送WoL报文 可反复启动虚拟机,但启动执行完毕会发现CaptureDevice Started: False,但后续仍正常抓包
2 生产环境中部署服务,多次发送WoL报文 只能启动虚拟机1次,启动后抓包进程立即停止,日志报的中止原因是ErrorWhileCapturing
3 生产环境中部署服务,捕获到WoL报文后什么也不做 可反复抓取到WoL报文,抓包进程和device均存活
4 生产环境中部署服务,WoL捕获后仅以命令行启动记事本 一切正常,多次发送WoL报文可反复启动记事本

随后我又将生产环境中的Virtualbox升级到7.08,结果依然没有改变。于是,可以初步得出结论,SharpPcap 4.3.0(最后一个支持.NET Framework的版本,目前最新版为6.0)在Windows 2012R2上存在Bug会在Virtualbox启动后异常中止抓包,而且该异常还无法被捕捉到,ErrorWhileCapturing的日志是OnCaptureStopped事件中报出来的。

折中的解决办法

既然SharpPcap没法使用更高的版本,Virtualbox使用了更高的版本也无济于事,只能通过折中的办法来解决了——线程守护/看门狗。即在OnCaptureStopped中重启抓包的线程。需要注意的是,不能无脑重启,需要设置一个线程启动失败的计数器,启动失败超过一定次数(我设定的是5次)就不再重启了,打日志出来,然后开摆。否则你的服务可能陷入“线程异常中止——线程重启”的死循环。而一旦线程启动成功,就将应将计数器归零。这是一个十分朴素的看门狗。

  • 本文标题:面向虚拟机Wake-on-LAN的Windows服务开发
  • 本文作者:Tsccai
  • 创建时间:2023-08-23 22:48:54
  • 本文链接:https://tsccai.github.io/2023/08/23/windows-service-development-for-vm-wol/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!