用 Go 實現(xiàn)底層 Socket 的 Wake-on-LAN 技術
在日常工作或運維自動化中,我們可能會遇到這樣的場景:
- 想遠程喚醒家里的 NAS 或服務器;
- 企業(yè)中控平臺需要遠程喚醒局域網(wǎng)中的某些設備;
- 想做一個能自動喚醒局域網(wǎng)機器的程序或服務。
這時,Wake-on-LAN(WOL)就是你的好朋友。今天我們就用Go語言手把手實現(xiàn)一個簡陋的WOL喚醒工具!
1. 什么是WOL?
什么是wol呢?下面是一段摘自wiki百科的簡單介紹,具體介紹如下所述:
Wake-on-LAN,簡稱WOL或WoL,中譯為“網(wǎng)絡喚醒”、“遠程喚醒”,是一種遠程喚醒技術及標準,功效在于讓休眠狀態(tài)或關機狀態(tài)的電腦,透過局域網(wǎng)的另一臺電腦對其發(fā)令,使其喚醒、恢復成運作狀態(tài),或從關機狀態(tài)轉成開機狀態(tài)。該消息通常由在連接到同一局域網(wǎng)的設備上執(zhí)行的程序發(fā)送到目標計算機。也可以使用子網(wǎng)定向廣播或 WoL 網(wǎng)關服務從另一個網(wǎng)絡發(fā)起消息。
2. WOL原理
這也是一段摘自wiki百科的一段描述,一般而言,WOL技術的遠程喚醒步驟如下:
電腦處在關機(或休眠)狀態(tài)時,機內(nèi)的網(wǎng)卡及主板部分仍保有微弱的供電,此微弱供電能讓網(wǎng)卡保有最低的運作能力,使網(wǎng)卡能聆聽來自電腦外部的網(wǎng)絡廣播信息,并對信息內(nèi)容進行偵測與解讀,一旦發(fā)現(xiàn)網(wǎng)絡廣播的內(nèi)容中有特定的“魔法數(shù)據(jù)包”(Magic Packet),就會對該數(shù)據(jù)包的內(nèi)容進行研判。
魔法數(shù)據(jù)包是以廣播方式發(fā)送的,廣播的方式與范疇可以是整個局域網(wǎng)(LAN),也可以是特定的子網(wǎng)(Subnet),同時魔法數(shù)據(jù)包內(nèi)會有某部(或一群)電腦的網(wǎng)絡地址數(shù)據(jù),網(wǎng)卡一旦解讀研判出所指的地址是自身所處的電腦時,網(wǎng)卡就會通知機內(nèi)的主板、電源供應器,開始進行開機(或喚醒)的程序。
3. 什么是魔法數(shù)據(jù)包?
魔法數(shù)據(jù)包當然是會變魔法的數(shù)據(jù)包啦,以下還是一段摘自wiki百科的描述具體如下:
魔法數(shù)據(jù)包(Magic Packet)是一個廣播性的幀(frame),透過端口7或9發(fā)送,可以使用無需建立連接(Connectionless protocol)的通信協(xié)議(如UDP、IPX)來傳遞,目前鑒于已很少采用Novell NetWare網(wǎng)絡操作系統(tǒng)的IPX協(xié)議而多選用UDP。
在魔法數(shù)據(jù)包內(nèi),每次都會先有連續(xù)6個"FF"(十六進制,換算成二進制即:11111111)的數(shù)據(jù),即:FF FF FF FF FF FF,在連續(xù)6個"FF"后則開始帶出MAC地址信息,有時還會帶出4字節(jié)或6字節(jié)的密碼,一旦經(jīng)由網(wǎng)卡偵測、解讀、研判(廣播)魔法數(shù)據(jù)包的內(nèi)容,內(nèi)容中的MAC地址、密碼若與電腦自身的地址、密碼吻合,就會啟動喚醒、開機的程序。
4. 用Golang編寫底層WOL代碼
我們下面用Go原生的syscall庫構建底層UDP Socket,通過廣播方式發(fā)送WOL數(shù)據(jù)包。
第一步:構造會魔法的數(shù)據(jù)包(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")
}
// 解碼為字節(jié)
macBytes, err := hex.DecodeString(macClean)
if err != nil {
return nil, fmt.Errorf("failed to parse MAC address: %v", err)
}
// 創(chuàng)建 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
}
解析:
- 這里我們先將MAC地址轉成字節(jié)數(shù)組;
- 然后拼接6字節(jié)廣播頭 + 16次重復MAC。
第二步:使用底層UDP Socket廣播發(fā)送
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)
// 發(fā)送數(shù)據(jù)包
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
}
解析:
- 使用syscall.Socket創(chuàng)建UDP Socket;
- 配置為廣播模式;
- 使用Sendto向255.255.255.255:9廣播發(fā)送數(shù)據(jù)包。
第三步:編寫入口主函數(shù)
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)
}
}
以上代碼我們就編寫好了,那么下面就是見證時刻的奇跡了,好激動啊,運行命令如下所示:
go run main.go
毫不意外程序運行是失敗的,因為我那臺祖?zhèn)鞯膚indows筆記本睡死了。有句話說得好,古人云,愛而不得的人,我們怎么叫都叫不醒,就如這臺電腦,猶如我那顆死透了的心??,從此水泥封心。
5. 使用注意事項
- 開啟 BIOS 中的 WOL 支持,網(wǎng)卡也要支持
- 關機狀態(tài)需有待機電源(即插著電的關機)
- 如果用 Linux,可以通過 ethtool 啟用網(wǎng)卡 WOL 功能:
sudo ethtool -s eth0 wol g
- 若在公網(wǎng)喚醒設備,需路由器設置端口轉發(fā)或VPN內(nèi)網(wǎng)
6. 總結
使用底層Socket方式構造并發(fā)送Wake-on-LAN包,在Go中非常適合構建系統(tǒng)級喚醒工具。相比起高層封裝方式,這種原生實現(xiàn)方式更靈活、更可控,也更適合你構建跨平臺或嵌入式場景的WOL工具。