用Go实现底层Socket的Wake-on-LAN技术

在日常工作或运维自动化中,我们可能会遇到这样的场景:

想远程唤醒家里的 NAS 或服务器;企业中控平台需要远程唤醒局域网中的某些设备;想做一个能自动唤醒局域网机器的程序或服务。

这时,Wake-on-LAN(WOL)就是你的好朋友。今天我们就用Go语言手把手实现一个简陋的WOL唤醒工具!

什么是WOL?

什么是wol呢?下面是一段摘自wiki百科的简单介绍,具体介绍如下所述:

Wake-on-LAN,简称WOL或WoL,中译为“网络唤醒”、“远程唤醒”,是一种远程唤醒技术及标准,功效在于让休眠状态或关机状态的电脑,透过局域网的另一台电脑对其发令,使其唤醒、恢复成运作状态,或从关机状态转成开机状态。该消息通常由在连接到同一局域网的设备上执行的程序发送到目标计算机。也可以使用子网定向广播或 WoL 网关服务从另一个网络发起消息。

WOL原理

这也是一段摘自wiki百科的一段描述,一般而言,WOL技术的远程唤醒步骤如下:

电脑处在关机(或休眠)状态时,机内的网卡及主板部分仍保有微弱的供电,此微弱供电能让网卡保有最低的运作能力,使网卡能聆听来自电脑外部的网络广播信息,并对信息内容进行侦测与解读,一旦发现网络广播的内容中有特定的“魔法数据包”(Magic Packet),就会对该数据包的内容进行研判。

魔法数据包是以广播方式发送的,广播的方式与范畴可以是整个局域网(LAN),也可以是特定的子网(Subnet),同时魔法数据包内会有某部(或一群)电脑的网络地址数据,网卡一旦解读研判出所指的地址是自身所处的电脑时,网卡就会通知机内的主板、电源供应器,开始进行开机(或唤醒)的程序。

什么是魔法数据包?

魔法数据包当然是会变魔法的数据包啦,以下还是一段摘自wiki百科的描述

魔法数据包(Magic Packet)是一个广播性的帧(frame),透过端口7或9发送,可以使用无需建立连接(Connectionless protocol)的通信协议(如UDP、IPX)来传递,目前鉴于已很少采用Novell NetWare网络操作系统的IPX协议而多选用UDP。

具体如下:魔法数据包(Magic Packet)是一个广播性的帧(frame),透过端口7或9发送,可以使用无需建立连接(Connectionless protocol)的通信协议(如UDP、IPX)来传递,目前鉴于已很少采用Novell NetWare网络操作系统的IPX协议而多选用UDP。

在魔法数据包内,每次都会先有连续6个"FF"(十六进制,换算成二进制即:11111111)的数据,即:FF FF FF FF FF FF,在连续6个"FF"后则开始带出MAC地址信息,有时还会带出4字节或6字节的密码,一旦经由网卡侦测、解读、研判(广播)魔法数据包的内容,内容中的MAC地址、密码若与电脑自身的地址、密码吻合,就会启动唤醒、开机的程序。

用Golang编写底层WOL代码

我们下面用Go原生的syscall库构建底层UDP Socket,通过广播方式发送WOL数据包。

第一步:构造会魔法的数据包(Magic Packet啊,他好会呀🌝)
复制
func createMagicPacket(mac string) ([]byte, error) { // 清理 MAC 格式 macClean := strings.ReplaceAll(strings.ReplaceAll(mac, ":", ""), "-", "") if len(macClean) != 12 { return nil, fmt.Errorf("invalid MAC address format") } // 解码为字节 macBytes, err := hex.DecodeString(macClean) if err != nil { return nil, fmt.Errorf("failed to parse MAC address: %v", err) } // 创建 Magic Packet packet := make([]byte, 6+(16*6)) for i := 0; i < 6; i++ { packet[i] = 0xFF } for i := 0; i < 16; i++ { copy(packet[6+i*6:], macBytes) } return packet, nil }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.

解析:

这里我们先将MAC地址转成字节数组;然后拼接6字节广播头 + 16次重复MAC。第二步:使用底层UDP Socket广播发送
复制
func sendMagicPacket(packet []byte, broadcastIP string, port int) error { fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) if err != nil { return fmt.Errorf("failed to create socket: %v", err) } defer syscall.Close(fd) // 启用广播 if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil { return fmt.Errorf("failed to set broadcast option: %v", err) } // 设置目标地址 dst := syscall.SockaddrInet4{Port: port} ip := net.ParseIP(broadcastIP).To4() if ip == nil { return fmt.Errorf("invalid broadcast IP address") } copy(dst.Addr[:], ip) // 发送数据包 if err := syscall.Sendto(fd, packet, 0, &dst); err != nil { return fmt.Errorf("failed to send magic packet: %v", err) } fmt.Println("Magic Packet sent via raw socket successfully!") return nil }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.

解析:

使用syscall.Socket创建UDP Socket;配置为广播模式;使用Sendto向255.255.255.255:9广播发送数据包。第三步:编写入口主函数
复制
func main() { mac := "00:11:22:33:44:55" // 目标设备MAC地址 broadcastIP := "255.255.255.255" // 广播地址 port := 9 // 常见UDP端口 packet, err := createMagicPacket(mac) if err != nil { fmt.Printf("Packet creation error: %v\n", err) return } if err := sendMagicPacket(packet, broadcastIP, port); err != nil { fmt.Printf("Failed to send packet: %v\n", err) } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.

以上代码我们就编写好了,那么下面就是见证时刻的奇迹了,好激动啊,运行命令如下所示:

复制
go run main.go1.

毫不意外程序运行是失败的,因为我那台祖传的windows笔记本睡死了。有句话说得好,古人云,爱而不得的人,我们怎么叫都叫不醒,就如这台电脑,犹如我那颗死透了的心❤️,从此水泥封心。

使用注意事项开启 BIOS 中的 WOL 支持,网卡也要支持关机状态需有待机电源(即插着电的关机)如果用 Linux,可以通过 ethtool 启用网卡 WOL 功能:
复制
sudo ethtool -s eth0 wol g1.
若在公网唤醒设备,需路由器设置端口转发或VPN内网总结

使用底层Socket方式构造并发送Wake-on-LAN包,在Go中非常适合构建系统级唤醒工具。相比起高层封装方式,这种原生实现方式更灵活、更可控,也更适合你构建跨平台或嵌入式场景的WOL工具。

参考链接🔗:https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E5%96%9A%E9%86%92

THE END