使用raw socket发送magic packet
文章目录
Magic Packet是进行网络唤醒的数据包,将这个数据包以广播的形式发到局域网上,与数据包中所关联的MAC相同的电脑就会被唤醒开机,通常我们都是使用UDP报文的形式来发送这个数据包,但实际上在进行网络唤醒的时候,只要报文中包含Magic Packet应该就可以唤醒相关的计算机,与IP协议、UDP协议没有任何关系,本文将试图抛开网络层(IP层)和传输层(TCP/UDP层),直接在数据链路层发出Magic Packet,并成功实现网络唤醒,本文将提供完整的源代码。阅读本文需要有较好的网络编程基础,本文对网络编程的初学者有一定难度。
1. Magic Packet
- 我比较喜欢把这个数据包称作"网络唤醒包";有很多地方把它翻译成"魔术包"或者"魔法数据包",我个人觉着太过表面,无法表达其实际的含义;本文将这个数据包称为"网络唤醒包"或者"Magic Packet",二者具有完全相同的含义;
- 以前写过一篇与嵌入式相关的文章《远程开机:一个简单的嵌入式项目开发》,在嵌入式环境下使用Magic Packet进行远程开机的小项目,有兴趣的读者可以参考;
Magic Packet就是一个指定格式的数据包,其格式为:6 个 0xff,然后16组需要被网络唤醒的电脑的 MAC 地址,比如需要被唤醒的电脑的 MAC 为:00:e0:2b:69:00:03,则Magic Packet为(16进制表述):1 2 3 4 5ff ff ff ff ff ff 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03 00 e0 2b 69 00 03- 关于 Magic Packet 的更多的信息请参考
- 对Magic Packet也没有什么好解释的,理论上只要一个报文中存在这个Magic Packet,那么有网络唤醒功能的NIC都会相应,What is NIC? NIC就是网卡,Network Interface Controller;
- 但是大多数的 802.11 的无线网卡是收不到 Magic Packet 的,这一点在wikipedia-Wake-On-Lan上有明确的说明;所以不要尝试在无线网卡上做网络唤醒,但是有一个叫做WoWLAN的标准是专门支持无线网卡的网络唤醒的,以后有时间的时候试一下;
- 通常发送Magic Packet是使用UDP广播的方式发,这方面的文章很多,有兴趣的读者可以百个度或者谷个歌去找一下,我的另一篇文章《远程开机:一个简单的嵌入式项目开发》也是用这种方式发的,这里就不赘述了;
2. 相关技术要点
以下要点将会在本文的范例程序中用到,在此需要简单回顾以下。
-
TCP/IP的五层网络模型(OSI 七层架构的简化版)
- 应用层
- 传输层(TCP/UDP)
- 网络层(IP)
- 数据链路层(Ethernet)
- 物理层
-
基于TCP/IP的数据报文的结构
- 一个基于TCP/IP的完整报文结构为:以太网报头 + IP报头 + TCP/UDP报头 + data,如下图:

-
以太网报头
- 以太网报头定义在头文件<linux/if_ether.h>中:
1 2 3 4 5struct ethhdr { unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ unsigned char h_source[ETH_ALEN]; /* source ether addr */ __be16 h_proto; /* packet type ID field */ } __attribute__((packed)); - h_dest字段为目的MAC地址,h_source字段为源MAC地址;
- h_proto表示当前数据包在网络层使用的协议,Linux支持的协议在头文件<linux/if_ether.h>中定义;通常在网络层使用的IP协议,这个字段的值是0x0800(ETH_P_IP);
- 但是本文中的 h_proto 字段要填写 0x842,很遗憾这个协议在头文件中没有定义,也基本找不到相关资料;
- 以太网报头定义在头文件<linux/if_ether.h>中:
-
raw socket
- 可以参考我的另两篇文章《Linux下如何在数据链路层接收原始数据包》和《如何使用raw socket发送UDP报文》,这里仅做一个简单回顾;
- 打开一个raw socket
1 2int sock_raw; sock_raw = socket(AF_PACKET, SOCK_RAW, 0); - 第三个参数还可以有其它选择,这个参数往往会对socket的接收产生影响,本文并不接收任何信息,所以对本文来说无关紧要。
-
struct sockaddr_ll
- 这个结构在<linux/if_packet.h>中定义,有关该结构的详细说明请参考其它文章,本文仅就相关字段做出说明;
- 这个结构与 IPv4 socket 编程中的结构(struct sockaddr_in)的作用类似,是用在raw socket上的一个地址结构,烦请自行理解,其中’ll’表示Low Level
1 2 3 4 5 6 7 8 9struct sockaddr_ll { unsigned short sll_family; __be16 sll_protocol; int sll_ifindex; unsigned short sll_hatype; // Hardware Address Type unsigned char sll_pkttype; // Packet Type unsigned char sll_halen; // Hardware Address Length unsigned char sll_addr[8]; // Address(Hardware Address) }; - sll_family为协议族,和建立raw socket是使用的协议族要一致,所以肯定是AF_PACKET;
- sll_protocol是标准的以太网协议类型,定义在头文件<linux/if_ether.h>中,通常情况下应该 ETH_P_IP(0x800) 表示IP协议,本文要填 0x842;
- sll_ifindex是网络接口的索引号,我们可以根据接口名称使用ioctl获得;
- sll_halen是硬件地址(MAC)的长度,ha是Hardware Address的意思,填常数 ETH_ALEN(定义在头文件<linux/if_ether.h>中);
- sll_addr是目的MAC地址
- 实际上,在发送数据时,由于sll_family和sll_protocol都是和socket中一样的,所以大多数情况下都可以不填,只要填sll_ifindex、sll_halen和sll_addr即可,但是本文的例子中,如果使用bind()绑定地址,应该尽量完整地填写,否则执行bind()是会出错。
3. 在数据链路层发送Magic Packet
-
先说一下目标,通常使用UDP发送Magic Packet的报文结构为:以太网报头 + IP报头 + UDP报头 + Magic Packet,我们的目标是:以太网报头 + Magic Packet
-
先看源程序,文件名为:magic-packet.c(点击文件名下载源程序)
-
报文是以广播的形式发出去的,发送广播时,以太网报头的目的MAC要全部填写0xff,(struct sockaddr_ll)中的sll_addr也要全部填写0xff;
-
尽管我们知道目的MAC,但是这个报文必须以广播报文发出,因为此时被唤醒的机器并没有开机,所以无法通过arp获知填写的MAC地址是否在局域网内,所以如果在以太网报头的h_dest中填上了要被唤醒的机器的MAC地址,报文是无法送达的;
-
关于使用ioctl获取接口索引号(Interface Index)和接口MAC的问题,请自行查找资料,或者参考文章《如何使用raw socket发送UDP报文》,这篇文章中有部分内容涉及到这个问题;
-
程序中写好了用send()或者用sendto()发送报文的代码,使用一个常量USING_SENDTO来控制,当USING_SENDTO为1时,将使用sendto()发送报文,否则使用send()发送报文;
-
如果使用send()发送报文,需要使用bind()绑定目的地址;
-
具体实践中,如果使用sendto()发送报文,(struct sockaddr_ll)中只需填写sll_ifindex即可,这个也许和运行环境有关,请自行测试;理论上说,只要编译通过,运行没有出错,就表示这个Magic Packet发送了出去,应该就可以起作用;
-
源程序中有详细的注释,其它也没有什么更多解释的。
-
编译:
gcc -Wall magic-packet.c -o magic-packet -
运行:
sudo ./magic-packet enp0s3 00:e0:2b:68:00:03 -
由于使用了raw socket,所以必须以root权限运行;
-
如果你填写的ifname和mac都正确的话,mac所对应的电脑应该被远程唤醒;
-
这个程序并不好调试,因为能够在数据链路层侦听的工具不多,加上使用的协议号为0x842,使得大多数工具都无法使用,wireshark应该是可以的,而且wireshark的wiki上说,它有专门针对0x842号协议的侦听,不过我没有试过,有感兴趣的读者可以试一下;
-
远程唤醒是需要硬件支持的,主板和网卡都要支持,但是目前绝大多数有线网卡都应该是支持的,但是可能要在BIOS和其它地方做一些设置,请自行搜索相关资料,并参考我的另一篇文章《远程开机:一个简单的嵌入式项目开发》
-
运行结果截图:

欢迎订阅 『网络编程专栏』
欢迎访问我的博客:https://whowin.cn
email: hengch@163.com

文章作者 whowin
上次更新 2023-02-09