Wake-on-LAN(WOL, 网络唤醒)是一种以太网或令牌环计算机标准,允许通过网络消息开启或唤醒睡眠模式的计算机。该消息通常由连接到同一局域网 (LAN) 的设备上运行的程序发送到目标计算机。也可以通过使用子网定向广播或 WoL 网关服务从另一个网络发起消息。它基于 AMD 的 Magic Packet 技术 ,该技术由 AMD 和惠普共同开发,并于 1995 年被提议作为标准。
简而言之,就是能在局域网下通过发送魔术包的方式唤醒计算机。这有什么好处呢?
试想一个场景,你的家里有一台配置比较高的电脑或服务器,安装一些开发环境,启动了一些服务:Nginx、Redis、Mysql、Kafka 等等。由于配置高又运行这么些服务,功耗也同样高,所以我们必然不会全天候运行,所以想过弄一个低功耗的小主机/小型服务器,7x24 小时不间断运行。 我考虑过迷你主机、Nas主机,但它们被设计的使用场景都不符合我的需求。看过不少品牌的迷你主机,它的性能通常比Nas高,但又不如我们的标准主机,而且它的扩展性也不如标准主机,如果运行我们开发所需的服务,它的功耗一样不会低,那么小的机身还要给它散热,有点太为难它的,那要它做甚? 至于Nas,它的设计目标在于数据存储和共享,通常是 7x24 小时不间断运行,所以要在功耗和性能上做平衡。 说来说去,又回到了问题的起点。那就换一种解决思路,能不能在需要的时候开启,不用的时候关闭呢?当然可以,这就需要用到 WOL 技术了。
要想开启 Windows 的 WOL 功能有两个部分需要设置:BIOS设置、网卡设置。
不同主板进入 BIOS 的方式不同,都可以谷歌得到,这里以我的微星主板为例。 电脑开启时按DEL
键进入,在SETTING
-> 高级
-> 唤醒事件设置
,允许网络唤醒
即可。同时还有一项PCIE设备唤醒
,字面意思很容易理解,网卡就是一种PCIE设备
,所以允许网络唤醒
其实也就是允许PCIE设备唤醒
,只不过微星把这两个概念拆开了。可能在有的主板上就只有一项PCIE设备唤醒
,打开它也是一样的。但PCIE设备
并不仅仅只有网卡,其他任何一个开发板只要是有 PCIE 接口可以插在主板上,那么它也可以成为唤醒设备,这其实就是留了一个开放接口
的意思,你可以做各种 DIY 实现。这样,我们就可以实现电脑在睡眠/休眠
中被唤醒了。
另外,在电源管理设置
中有一项ERP/ErP Ready
,它默认是打开的,如果想要电脑在关机下也能被唤醒 ,那就需要关闭它。ErP的意思是与能源有关的产品,它与欧盟对各种电子产品(包括个人电脑)通过的环境法规有关。启用 BIOS 里的 ErP 模式或使用 ErP Ready 设置,将使你的个人电脑在关闭时,可以关闭流向所有组件的电源,将总的电力消耗减少到1瓦或更少。 说人话就是,禁用 ErP 在关机下几乎不耗电池,此时也不允许你用鼠标或键盘唤醒电脑,不能对传入的局域网信号作出反应,也就是说远程网络唤醒功能也因此失效了,那我们自然要禁用它。可能你会担心禁用后会好很多电吗?实际上并不会,禁用 ErP 后也就比启用它在关机下的功能多 1~2W,但这根本无所谓。按照民电 0.68元/度,2W 的设备 7x24 小时不间断运行,一年下来需要 11.91元的电费,相对于你在这一年里的开销来说,这不过于九牛一毛。
开机进入系统,按下win+R
输入ncpa.cpl
会打开网络连接
,选择你当前使用的那个网卡(插的是网线就选以太网
,连的 wifi 就选WLAN
),找到属性
-> 配置
-> 电源管理
,勾选允许此设备唤醒计算机
和只允许幻数据包唤醒计算机
(可选,但建议开启),并取消勾选允许关闭计算机关闭此设备以节约电源
。
在 ACPI(高级配置与电源接口)规范中,定义了从 S0 到 S5 的一系列电源状态,用于描述操作系统如何控制计算机的电源管理。
状态 名称 描述 S0 工作状态(Working) 系统完全运行,所有硬件设备正常供电 S1 待机状态(Standby) CPU 停止执行指令,但保持供电,内存保留内容,唤醒非常快 S2 深度待机状态(Deeper Sleep) 类似 S1,但 CPU 和缓存完全断电;更省电,唤醒稍慢。很少使用 S3 睡眠状态(Sleep/Suspend to RAM) 内存保持供电 ,CPU 和外设断电;唤醒速度较快,常见的“睡眠”模式S4 休眠状态(Hibernate/Suspend to Disk) 将内存内容保存到硬盘 ,然后全部断电;唤醒时从磁盘恢复,速度较慢,但更省电S5 关机状态(Soft Off) 系统完全关闭,几乎无功耗,但仍可通过特定事件(如唤醒信号)启动
可能之前我们会把睡眠(S3)和休眠(S4)弄混淆,事实上它们的定义标准是不同的。由于 RAM 的读写速度非常高,当我们从睡眠模式下唤醒到进入系统页面,通常只需要 2~3 秒;但硬盘的读写速度远不如 RAM ,即便是一个 SSD 硬盘,从休眠模式下唤醒到进入系统页面,可能也需要 7~8 秒。 在 Windows11 系统中,我们发现只能看到睡眠
,其实默认下休眠
是被隐藏了,因为 Windows11 系统的关机
和休眠
就比较类似了,一样是把工作内容保存在硬盘,使得开机后可以迅速恢复。
按下win+R
输入control
打开控制面板
,选择电源选项
。在左侧的选择电源按钮的功能
中可以看到休眠
是没有被勾选的,我们可以勾选上,这样在按下Win
键并点击关机
按钮时就可以看到休眠
的选项了。 同时,我们注意的电源选项中还有一项启用快速启动(推荐)
,它是默认被勾选的,正是由于启用了它,才得以让关机
的效果和休眠
类似,Windows 刻意隐藏了休眠
开关,想用快速启动
下的关机来达到休眠
效果,也避免了休眠
和睡眠
的含义比较接近,导致用户感觉到歧义。
此时,电脑就设置好了 WOl 唤醒了。可以通过Fing
应用(支持Android和IOS)其中的LAN唤醒
来验证,IOS 系统亦可下载Awake
应用(免费无广,且可集成在快捷指令
中)验证。如果有 OpenWrt 的软路由系统,可以安装luci-i18n-wol-zh-cn
,将网络唤醒
集成在 OpenWrt 中,便于操作。
至此,就已经设置好了主机网络唤醒。
上面说的都是把主机从睡眠/休眠/关机
状态下唤醒,那么我们还想要在主机醒着的状态下进入到睡眠/休眠/关机
状态如何做呢?这里当然不是要说在电脑上通过点击来操作,这样没有意思,我们需要通过指令 的方式让主机进入到睡眠/休眠/关机
状态。
通过命令行(cmd)窗口执行shutdown --help
可以看到指令支持的操作如下:
用法: shutdown [/ i | / l | / s | / sg | / r | / g | / a | / p | / h | / e | / o ] [/ hybrid ] [/ soft ] [/ fw ] [/ f ]
[/ m \\ computer ][/ t xxx][/ d [p|u:]xx:yy [/ c " comment " ]]
没有参数 显示帮助。这与键入 /? 是一样的。
/? 显示帮助。这与不键入任何选项是一样的。
/ i 显示图形用户界面 ( GUI ) 。
这必须是第一个选项。
/ l 注销。这不能与 / m 或 / d 选项一起使用。
/ s 关闭计算机。
/ sg 关闭计算机。在下一次启动时,如果启用了
自动重启登录,则将自动登录并锁定上次交互用户。
登录后,重启任何已注册的应用程序。
/ r 完全关闭并重启计算机。
/ g 完全关闭并重启计算机。重新启动系统后,
如果启用了自动重启登录,则将自动登录并
锁定上次交互用户。
登录后,重启任何已注册的应用程序。
/ a 中止系统关闭。
这只能在超时期间使用。
与 / fw 结合使用,以清除任何未完成的至固件的引导。
/ p 关闭本地计算机,没有超时或警告。
可以与 / d 和 / f 选项一起使用。
/ h 休眠本地计算机。
可以与 / f 选项一起使用。
/ hybrid 执行计算机关闭并进行准备以快速启动。
必须与 / s 选项一起使用。
/ fw 与关闭选项结合使用,使下次启动转到
固件用户界面。
/ e 记录计算机意外关闭的原因。
/ o 转到高级启动选项菜单并重新启动计算机。
必须与 / r 选项一起使用。
/ m \\ computer 指定目标计算机。
/ t xxx 将关闭前的超时时间设置为 xxx 秒。
有效范围是 0-315360000 ( 10 年 ) ,默认值为 30 。
如果超时期限大于 0 ,则 / f 参数为
/ f 参数。
/ c " comment " 注释重启或关闭的原因。
最多允许 512 个字符。
/ f 强制关闭正在运行的应用程序而不事先警告用户。
当大于 0 的值为
时,隐含 / f 参数 则默示为 / f 参数。
/ d [p|u:]xx:yy 提供重新启动或关闭的原因。
p 指示重启或关闭是计划内的。
u 指示原因是用户定义的。
如果未指定 p 和 u,则
重新启动或关闭 是计划外的。
xx 是主要原因编号 ( 小于 256 的正整数 ) 。
yy 是次要原因编号 ( 小于 65536 的正整数 ) 。
此计算机上的原因:
( E = 预期 U = 意外 P = 计划内, C = 自定义 )
这里关注几个我们需要的:
指令 效果 shutown /s 关机 shutown /h 休眠 shutown /r 重启
但是,我们没有看到睡眠
的指令。经过一番搜索,找到两条指令
可以实现睡眠或休眠
的效果:rundll32.exe powrprof.dll,SetSuspendState 0,1,0
或rundll32.exe powrprof.dll,SetSuspendState Sleep
。这两种指令经过验证效果是一样的。 为什么说是实现睡眠或休眠
的效果呢?即睡眠
和休眠
只能二选一,那什么时候是睡眠,什么时候是休眠呢?
默认情况下,休眠
功能是启用的,那么上面的指令
效果就是休眠,则它等价于shutown /h
。可以通过powercfg -h off
(powershell 管理员权限)来关闭休眠
功能,此时shutdown /h
指令会失效,同时在电源选项
中亦不存在休眠
和启用快速启动(推荐)
的设置,而指令
的效果也就变成睡眠
了。所以,需要斟酌一下,到底要不要关闭休眠
功能。如果关闭休眠
后想再次打开,可执行powercfg -h on
。 如何分辨是从睡眠中醒来,还是从休眠中醒来?查看主机唤醒时,显示器有没有显示主板BIOS的图案,如果有就说明是从休眠中醒来,否则不是。
如何便捷地通过 iPhone 在局域网下唤醒/休眠主机?这里我总结了一套实践经验。 因为 IOS 的快捷指令中支持 SSH 连接并发送指令,那么完全可以通过 SSH 连接到 OpenWrt/Windows 来实现唤醒/休眠主机,但是考虑到操作统一性和便捷性,我实际的做法是:
触发脚本
然而在实操踩坑中发现,对于shutdown /s
(关机)和shutdown /r
(重启)的指令响应逻辑没问题,但是睡眠
或休眠
就有问题了,直接执行相关命令会导致,主机刚进入睡眠
或休眠
状态又立即被唤醒了。 因此我在 Windows 用户目录下放了两个bat
脚本,定义了临时触发器来执行操作,供 OpenWrt 通过 SSH 调用,具体如下。
trigger_sleep.bat trigger_hibernate.bat
trigger_sleep.bat
@ echo off
setlocal
:: 获取当前时间 + 1 分钟
for /f " tokens=1,2 delims=: " %% a in ( " % time % " ) do (
set /a hh = 1 %% a %% 100
set /a mm = 1 %% b + 1
)
if % mm % GEQ 60 (
set /a mm = 0
set /a hh += 1
)
if % hh % GEQ 24 set /a hh = 0
:: 格式化为 HH:MM(确保补零)
set hh = 0 % hh %
set mm = 0 % mm %
set hh =% hh :~ -2 %
set mm =% mm :~ -2 %
:: 创建一次性计划任务,执行“睡眠”
schtasks /create /tn " TriggerSleep " /tr " rundll32.exe powrprof.dll,SetSuspendState 0,1,0 " /sc once /st % hh % : % mm % /f > nul
schtasks /run /tn " TriggerSleep " > nul
echo Will sleep at % hh % : % mm %
endlocal
trigger_hibernate.bat
@ echo off
setlocal
:: 获取当前时间 + 1 分钟
for /f " tokens=1,2 delims=: " %% a in ( " % time % " ) do (
set /a hh = 1 %% a %% 100
set /a mm = 1 %% b + 1
)
if % mm % GEQ 60 (
set /a mm = 0
set /a hh += 1
)
if % hh % GEQ 24 set /a hh = 0
:: 格式化为 HH:MM(确保补零)
set hh = 0 % hh %
set mm = 0 % mm %
set hh =% hh :~ -2 %
set mm =% mm :~ -2 %
:: 创建一次性计划任务
schtasks /create /tn " TriggerHibernate " /tr " shutdown /h " /sc once /st % hh % : % mm % /f > nul
schtasks /run /tn " TriggerHibernate " > nul
echo Will hibernate at % hh % : % mm %
endlocal
这两个脚本,trigger_sleep.bat
是用来触发睡眠
指令,trigger_hibernate.bat
是用来触发休眠
指令。
开启SSH
如果想通过 SSH 来管理 Windows 系统,就需要安装OpenSSH
服务,Windows11 默认已经安装了 SSH 的客户端,此时我们安装服务端。 有两种方式安装:
方式一: 进入设置
->系统
->可选功能
->添加可选功能
,搜索OpenSSH 服务器
安装。
方式二:
# 安装
Add-WindowsFeature - Name OpenSSH - Client , OpenSSH - Server
# 安装完成后就自动运行了
# 查看
Get-Service - Name sshd
# 连接测试
ssh localhost
设置为开机自启:按WIN
键搜索计算机管理
,然后打开服务
找到OpenSSH SSH Server
,修改属性将启动类型
调整为自动(延迟启动)
。
SSH密钥登录
可以借助Git bash 生成密钥,执行ssh-keygen -t rsa -f ~/.ssh/id_rsa
(生成RSA密钥)或ssh-keygen -t ed25519 -f ~/.ssh/id_rsa
(生成ed25519密钥),前者广泛兼容,后者安全性更高,随便选择一种即可。 然后把生成的密钥对中的公钥内容复制到 Windwos 用户目录下的.ssh\authorized_keys
文件中,如果文件不存在可自行创建,注意文件没有后缀。示例:
authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCC9P8z96xo1EcfejdrvEt/6Tc2qhX6FhLevcqjhNvQYPYmQaMwZElsi1QAABAQCC9P8z96xo1EcfejdrvdrvEt/6Tc2q root@ImmortalWrt
接着就是 Windows 的特有操作,更改authorized_keys
文件权限,否则无法通过密钥登录。以下命令二选一 即可:
# 远程通过 ACL 更改权限
ssh --% mayee@192.168.0.88 icacls.exe " C:\Users\mayee\.ssh\authorized_keys " /inheritance:r /grant " Administrators:F " /grant " SYSTEM:F "
# 服务端修改权限
icacls.exe " C:\Users\mayee\.ssh\authorized_keys " /inheritance:r /grant " Administrators:F " /grant " SYSTEM:F "
在 Windows OpenSSH 中,默认的授权密钥存放位置为ProgramData\ssh\administrators_authorized_keys
,此位置对应为管理用户权限。因此需要修改默认授权文件位置。通过文本编辑器(如 VSCode)打开ProgramData\ssh\sshd_config
,修改以下条目(需要管理员权限,也可以把文件复制出去,改完后再覆盖回来):
sshd_config
#允许公钥授权访问,确保条目不被注释
PubkeyAuthentication yes
#授权文件存放位置,确保条目不被注释
AuthorizedKeysFile . ssh / authorized_keys
#可选,关闭密码登录,提高安全性
PasswordAuthentication no
#注释掉默认授权文件位置,确保以下条目被注释
#Match Group administrators
# AuthorizedKeysFile __PROGRAMDATA__ / ssh / administrators_authorized_keys
# 注意,OpenSSH 8.8 及以上版本中,默认禁用了使用 SHA -1 哈希算法的 RSA 签名,如果你用的 RSA 密钥就需要加这两行,ed25519 密钥则不需要加
HostKeyAlgorithms + ssh - rsa
PubkeyAcceptedAlgorithms + ssh - rsa
重启 OpenSSH 服务:在 PowerShell 中执行Restart-Service sshd
(管理员权限)。 相关指令:
Get-Service sshd # 查看状态
Stop-Service sshd # 关闭服务
Restart-Service sshd # 重启服务
前文提到可以安装luci-i18n-wol-zh-cn
,它是一个可视化的操作页面,功能实现依赖的是etherwake
,也就是说只安装etherwake
用命令行的方式操作就可以了,那样连 OpenWrt 后台页面都不用进。
可以在 OpenWrt 后台页面软件包
中安装etherwake
,或者执行命令opkg install etherwake
。
在 OpenWrt 中,LuCI(Lua Configuration Interface) 是其默认的 Web 界面管理系统,LuCI 本质是 一套用 Lua 编写的 Web 应用逻辑,是一种让 Web 服务器运行外部程序(通常是脚本)的标准接口协议,用来处理动态网页请求。但它并不自带服务器功能,它必须运行在一个 Web 服务器之上,而 uHTTPd 是 OpenWrt 默认提供的轻量级 Web 服务器。
文件 说明 /etc/config/luci
LuCI 的主配置文件,如语言、主题、界面行为等设置 /etc/config/uhttpd
uHTTPd 的配置文件,控制 Web 服务监听地址、端口、SSL 等 /etc/config/rpcd
控制 LuCI 使用的 UBUS RPC 权限(例如哪些用户可以远程读写配置)
cat /etc/config/uhttpd
查看配置文件:
uhttpd
config uhttpd ' main '
list listen_http ' 0.0.0.0:80 '
list listen_http ' [::]:80 '
# 因为我们主要是内网访问,所以注释掉 443 端口的监听,后面留作他用
#list listen_https '0.0.0.0:443'
#list listen_https '[::]:443'
option redirect_https ' 0 ' # http 不要自动重定向到 https
option home ' /www ' # Web 根目录,访问静态页面文件的起点
option rfc1918_filter ' 1 ' # 拒绝非私有网段的 IP 访问(例如来自公网 IP),防止 uhttpd 被意外暴露到公网
option max_requests ' 50 ' # 每个进程最多处理 50 个请求
option max_connections ' 100 ' # 最多同时允许 100 个连接
option cert ' /etc/uhttpd.crt '
option key ' /etc/uhttpd.key '
option cgi_prefix ' /cgi-bin ' # CGI 请求的路径前缀
list lua_prefix ' /cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua ' # 将所有 /cgi-bin/luci 开头的请求交由指定的 Lua 脚本处理,这就是 LuCI 的运行入口。/usr/lib/lua/luci/sgi/uhttpd.lua 是 uHTTPd 专用的 LuCI CGI 启动器
option script_timeout ' 3600 ' # 设置 CGI 脚本最长执行时间为 3600 秒(1 小时),适合处理比较慢的后台任务
option network_timeout ' 30 ' # 客户端连接最长空闲时间(秒)。超过 30 秒没有数据就断开连接
option http_keepalive ' 20 ' # 单个连接最多保持 20 次复用,利于浏览器访问多个资源(JS/CSS)
option tcp_keepalive ' 1 ' # 开启 TCP keepalive,检测空闲连接是否掉线
option ubus_prefix ' /ubus ' # 设置 uHTTPd 暴露的 UBUS HTTP API 前缀为 /ubus,用于 RPC 接口
option interpreter ' .sh=/bin/sh ' # 定义 .sh 脚本用 /bin/sh 来解释,便于创建简单的 CGI 脚本页面
config cert ' defaults '
option days ' 397 ' # 自签证书有效期 397 天
option key_type ' ec '
option bits ' 256 '
option ec_curve ' P-256 '
option country ' ZZ '
option state ' Somewhere '
option location ' Unknown '
option commonname ' ImmortalWrt '
可以看到服务器文件都放在/www
目录下,cgi 的前缀是cgi-bin
。因此,为了便捷我我们在/www/cgi-bin
目录下新增一个文件wol
:
wol
#!/bin/sh
# 主机 MAC
mac = " A8:DD:77:CC:00:99 "
user = " mayee "
host = " 192.168.0.88 "
# 从 URL 路径获取参数
p1 =$( echo " $PATH_INFO " | sed ' s#^/## ' )
# 获取 GET 参数
p2 =$( echo " $QUERY_STRING " | sed -n ' s/.*cmd=\([^&]*\).*/\1/p ' )
# 优先取路径参数,没有则取 GET 参数,还没有就是空
if [ -n " $p1 " ]; then
cmd = " $p1 "
elif [ -n " $p2 " ]; then
cmd = " $p2 "
else
cmd = ""
fi
reply (){
echo " Content-type: text/plain; charset=utf-8 "
echo "" # http 格式要求必须空一行
echo " $1 "
}
remote_cmd () {
# -i 用来指定私钥的位置
ssh -i /root/.ssh/id_rsa $user @ $host " $1 "
}
case " $cmd " in
ping )
# -c 发送一次,-t 超时 10ms(内网环境下 ping 不会有高延迟,10ms 足够长了)。成功返回 1,失败返回 0
fping -c 1 -t 10 $host > /dev/null && reply 1 || reply 0
;;
sleep )
result =$( remote_cmd trigger_sleep.bat )
reply $result
;;
wake )
# -i 用来指定网卡名,参数可选
etherwake -i br-lan $mac && reply packet snet
;;
shutdown )
result =$( remote_cmd shutdown /s /f /t 1 ) # 或者用 shutdown /p
reply $result
;;
reboot )
result =$( remote_cmd shutdown /r /f /t 1 )
reply $result
;;
hibernate )
result =$( remote_cmd trigger_hibernate.bat )
reply $result
;;
*)
reply cmd_error: $cmd
;;
esac
然后可以通过http://192.168.0.88/cgi-bin/wol/<cmd>
访问。cmd 可选值:
ping
:检测主机是否在线sleep
:睡眠hibernate
:休眠wake
:唤醒shutdown
:关机reboot
:重启IOS 快捷指令请求 OpenWrt 脚本的逻辑流程如下:
上述这套打法可以轻松实现在局域网内,用 iPhone 手机管理主机。可能你会说只能在局域网中使用还是有点鸡肋啊,能不能不在局域网也可以管理主机呢?当然可以,这也是我们下一篇文章的主题:Lucky内网穿透实践 。