
为什么是虚拟机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和相应的虚拟机的
在部署服务时,需要填入安装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 | // WoLService.cs |
好了,现在服务不会那么轻易挂掉了,但通过日志发现,在启动虚拟机后,抓包的线程就死掉了,抓包也中止了,所以后续无法再响应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次)就不再重启了,打日志出来,然后开摆。否则你的服务可能陷入“线程异常中止——线程重启”的死循环。而一旦线程启动成功,就将应将计数器归零。这是一个十分朴素的看门狗。