您当前的位置:首页 > 计算机 > 文件格式与编码

WOL原理:网络远程唤醒 WOL Magic Packet

时间:03-02来源:作者:点击数:

WOL原理

WOL(Wake on Lan),即局域网唤醒,从根本上来说是硬件设备提供的一项管理功能,该功能可以当电脑处于关机或休眠状态时,通过给网卡发送特定的数据包从而命令网卡向主板发送开机指令,进而实现上电开机.可见,网络唤醒需要硬件(主要是主板和网卡)的支持.

Magic Packet白皮书介绍:

The basic technical details of Magic Packet Technologyare simple and easy to understand. There is also a sec-ond set of details, which will be implementation spe-cific. In other words, silicon- or gate-levelimplementations of Magic Packet Technology may dif-fer from AMD's approach and be completely interoper-able, as long as the basic feature set is maintained.

https://wenku.baidu.com/view/d5a3282e453610661ed9f487.html

硬件设置

进入BIOS,将“Power Management Setup”中的“Wake Up On LAN”或“Resume by LAN”项设置为“Enable”或“On”,类似于这样,因为主板不一样,BIOS设置位置有可能有差异

比如我的主板设置是在:Setting-Advanced-Wake up event setup - Resume By PCI-E Device 设置为Enable.

有的人说还要设置boot的第一启动项为network,我试过不需要滴!

软件设置

BIOS设置好了,硬件已经满足条件了,然后需要OS级别的软件设置

Windows设置:

设备管理器-网卡设备-属性-高级, 在列表里能找到“Wakeup Capabilities(唤醒功能)”设置值为“MagicPacket”或”Both”.

Linux设置:

先通过 ifconfig 查看要wol的网卡.

这里需要说下,好多网友说没有eth0, 对! 我也没有这个网卡的信息,因为你用的肯定是Ubuntu的衍生版,在Ubuntu 16.04之后以太网卡名称由eth0,变成了enp3s0,还有systemd替换掉了initd来引导系统,参考:Linux网卡命名enp3s0说明

用ethtool命令 打印网卡信息

sudo ethtool enp3s0

打印结果:

....
....
Supports Wake-on: pumbg
    Wake-on: g
    Current message level: 0x00000033 (51)
                   drv probe ifdown ifup

Wake-on 参数:

  • d 表示禁用disable
  • g表示启用great

如果wake-on参数为d就要启用wol, 启用命令:

sudo ethtool -s enp3s0 wol g

必须要用sudo 管理员权限否则会提示:  Cannot get current wake-on-lan settings: Operation not permitted

  • -s 参数是修改以太网设备设置

supports wake-on 中的参数

  • p Wake on phy activity
  • u Wake on unicast messages
  • m Wake on multicast messages
  • b Wake on broadcast messages
  • a Wake on AR

获取MAC地址

Windows 直接在网络设备-属性里面, linux使用ifconfig 命令查看

enp3s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.213  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::a680:65a4:a006:5bbc  prefixlen 64  scopeid 0x20<link>
        ether d8:cb:8a:3f:32:d2  txqueuelen 1000  (以太网)
        RX packets 890331  bytes 1003253740 (1.0 GB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 555010  bytes 73345907 (73.3 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

网络唤醒

到这;已经有了BIOS的支持,有了OS wake-on的支持,又知道了MAC地址,说明被控制(唤醒)机已经Ready了.

局域网唤醒

现在我们需要局域网的电脑或者设备去访问,访问不是随随便便访问的,因为Magic Packet是一种协议规则,必须要用规则发送,关于规则可看下一个段落解析Magic Packet包

我们用GUI工具去访问,常用的WOL工具有

WakeOnLanGui

下载地址:https://www.depicus.com/wake-on-lan/wake-on-lan-gui

WakeMeOnLan

下载地址:http://wakemeonlan.findmysoft.com

参数IP地址,MAC地址,任意一个端口号,IP地址也可以直接填写255.255.255.255

Linux下我没有找到GUI工具,只有wakeonlan命令了

sudo apt install wakeonlan

唤醒目标主机:

wakeonlan d8:cb:8a:3f:32:d2

输出:

Sending magic packet to 255.255.255.255:9 with d8:cb:8a:3f:32:d2

局域网手机唤醒

在同一个局域网下,手机也可以唤醒目标主机

Android APP: Wake On Lan

iphone APP:RemoteBoot

下载地址:自己搜.....

互联网远程唤醒

虽然 WOL (Wake on Lan) 网络唤醒原本的设计就是 LAN 局域网环境下使用的,但其实我们也是可以想办法让其在 WAN 广域网 (即互联网) 下使用——Wake On Wan。这样,在公司唤醒家里的电脑(在家唤醒公司电脑...........)

第一步:设置端口映射(虚拟服务器)

因为广域网不知道我们局域网内对应的主机是哪一个,所以只访问外网的IP不会把Magic Packet包发到局域网指定的电脑上.我们需要路由器端口映射的支持

登陆路由器找到 传输控制-NAT设置-虚拟服务器

路由器必须为顶级路由而非二级即二级以下

如果路由器LAN设置的是DHCP动态分配IP,有可能这次分配的局域网IP与重启之后局域网IP不统一(如果设备少于DHCP分配区间,IP会一直续租), 所以最好做一个静态地址分配或者IP与MAC绑定.

对于外网,如果IP是运营商固定IP可以直接使用公网IP,如果非固定IP(自动获取IP与PPPoE拨号)可以通过花生壳进行DDNS动态域名解析.使用动态域名替代公网IP.

第二步:查看公网IP

通过百度 ip 查看对应公网IP

第三步:网络访问目标

OK! 此时,可以通过手机APP进行互联网的唤醒了,关闭WiFi,打开4G.好吧,不关WiFi也行.

depicus 提供了在线远程唤醒   (现在貌似唤不起来了!!!)

https://www.depicus.com/wake-on-lan/woli

网站这样解释:

Wake on Lan Magic Packets can be sent over the Internet - why not try waking up one of your machines with our free Wake On Wan Service. Want a quick way to use this page ?

Bookmark https://www.depicus.com/wake-on-lan/woli?m=001143BDA600&i=82.110.108.30&s=255.255.255.255&p=4321 will get you straight there without the need to press those pesky send buttons.

换句话说,我们可以不用下载APP,只要保存一个书签,当需要的时候访问书签即可.

https://www.depicus.com/wake-on-lan/woli?m=d8cb8a3f32d2&i=58.37.39.82&s=255.255.255.255&p=4321 

TeamViewer 自带Lan网络唤醒

在TeamViewer-其他-常规-网络设置中就有自带的Lan网络唤醒

填写公共地址IP或端口映射对应的NAT网址,手机端TeamViewer登陆对应的账户,当主机关机时我们就可以通过手机端TeamViewer唤醒电脑

解析Magic Packet包

我们已经知道,Magic Packet是一种协议规则,必须遵从规则发送,Magic Packet的包格式很简单,首先是六个FF,然后是重复十六次待唤醒电脑的MAC

FF FF FF FF FF FF d8 cb 8a 3f 32 d2 d8 cb 8a 3f 32 d2 ....... 

理论上可以在任意网络封包中打包Magic Packet,不过一般选择UDP或IPX

WOL 技术被提出了将近20年,绝大多数的现代网卡都支持在超低功耗下监听特定的报文,如 ARP。如果设备网卡接收到一个与自己 MAC 地址相同的幻数据包,则网卡会向计算机的电源或主板发出信号以唤醒计算机。大部分的幻数据包在数据链路层(OSI模型第2层)上发送,当发送时,使用广播地址广播到给定的网络上,不使用IP地址(OSI模型第3层)。当然这是绝大部分情况,幻数据包也可以使用特定的 IP 地址进行发送。

幻数据包最简单的构成是6字节的255(FF FF FF FF FF FF FF),紧接着为目标计算机的48位MAC地址,重复16次,数据包共计102字节。有时数据包内还会紧接着4-6字节的密码信息。这个帧片段可以包含在任何协议中,最常见的是包含在 UDP 中。

例如 MAC 地址为 11 22 33 44 55 66 的目标计算机,幻数据包的格式为:

幻数据包还有一些基本限制条件:

  • 需要知道目标计算机 MAC 地址
  • 不提供送达确认
  • 可能无法在局域网之外工作
  • 需要硬件进行支持

创建幻数据包

项目地址:https://github.com/ZhangGaoxing/wake-on-lan

该项目为 Xamarin 跨平台项目,包含 Xamarin.Android 与 UWP 。支持自动扫描添加局域网设备。

Python代码:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
Small module for use with the wake on lan protocol.

"""
from __future__ import absolute_import
from __future__ import unicode_literals

import argparse
import socket
import struct
import re


BROADCAST_IP = '255.255.255.255'
DEFAULT_PORT = 9


def create_magic_packet(macaddress):
    """
    Create a magic packet.

    A magic packet is a packet that can be used with the for wake on lan
    protocol to wake up a computer. The packet is constructed from the
    mac address given as a parameter.

    Args:
        macaddress (str): the mac address that should be parsed into a
            magic packet.

    """
    if len(macaddress) == 12:
        pass
    elif len(macaddress) == 17:
        sep = macaddress[2]
        macaddress = macaddress.replace(sep, '')
    else:
        raise ValueError('Incorrect MAC address format')

    # Pad the synchronization stream
    data = b'FFFFFFFFFFFF' + (macaddress * 16).encode()
    send_data = b''

    # Split up the hex values in pack
    for i in range(0, len(data), 2):
        send_data += struct.pack(b'B', int(data[i: i + 2], 16))
    return send_data


def send_magic_packet(*macs, **kwargs):
    """
    Wake up computers having any of the given mac addresses.

    Wake on lan must be enabled on the host device.

    Args:
        macs (str): One or more macaddresses of machines to wake.

    Keyword Args:
        ip_address (str): the ip address of the host to send the magic packet
                     to (default "255.255.255.255")
        port (int): the port of the host to send the magic packet to
               (default 9)

    """
    packets = []
    ip = kwargs.pop('ip_address', BROADCAST_IP)

    port = kwargs.pop('port', DEFAULT_PORT)
    for k in kwargs:
        raise TypeError('send_magic_packet() got an unexpected keyword '
                        'argument {!r}'.format(k))



    for mac in macs:
        packet = create_magic_packet(mac)
        packets.append(packet)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

    # 如果输入的是域名,将域名转换为IP
    ipv4_regex = re.compile(r'(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}',re.IGNORECASE)
    if not ipv4_regex.match(ip):
        ip=socket.gethostbyname(ip)
    sock.connect((ip, port))
    for packet in packets:
        sock.send(packet)
    sock.close()
    print('sent to '+ip)


def main(argv=None):
    """
    Run wake on lan as a CLI application.

    """
    parser = argparse.ArgumentParser(
        description='Wake one or more computers using the wake on lan'
                    ' protocol.')
    parser.add_argument(
        'macs',
        metavar='mac address',
        nargs='+',
        help='The mac addresses or of the computers you are trying to wake.')
    parser.add_argument(
        '-i',
        metavar='ip',
        default=BROADCAST_IP,
        help='The ip address of the host to send the magic packet to.'
             ' (default {})'.format(BROADCAST_IP))
    parser.add_argument(
        '-p',
        metavar='port',
        type=int,
        default=DEFAULT_PORT,
        help='The port of the host to send the magic packet to (default 9)')
    args = parser.parse_args(argv)
    send_magic_packet(*args.macs, ip_address=args.i, port=args.p)


if __name__ == '__main__':  # pragma: nocover
    main()

terminal执行:

python3 WakeOnWan.py -i test.tpddns.cn -p 9  d8:cb:8a:3f:32:d2
python3 WakeOnWan.py -i 192.168.1.105 -p 9  d8:cb:8a:3f:32:d2

C++ 代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

void fill_magic_buf(void *magic_buf, void *mac)
{
        int i;
        char *ptr;

        ptr = magic_buf;
        memset(ptr, 0xFF, 6);
        ptr += 6;

        for(i = 0; i < 16; ++i) {
                memcpy(ptr, mac, 6);
                ptr += 6;
        }
}

void usage(void)
{
        printf("usage...\n");
}

int main(int argc, char **argv)
{
        int s;
        int packet_num = 10;
        char c;

        unsigned char mac[6] = {0x00, 0x1A, 0x92, 0xE5, 0x1B, 0xA7};
        char dstip[256] = "192.168.9.180";
        int port = 9;

        struct sockaddr_in address;
        char magic_buf[6 + 6 * 16] = {0};

        daemon(0,0);    /* run in background */

        while((c = getopt(argc, argv, "d:m:p:")) != -1) {
                switch(c) {
                case 'd':
                        strcpy(dstip, optarg);
                        break;
                case 'm':
                        sscanf(optarg, "%x:%x:%x:%x:%x:%x",
                                (unsigned int*)&mac[0], (unsigned int*)&mac[1],
                                (unsigned int*)&mac[2], (unsigned int*)&mac[3],
                                (unsigned int*)&mac[4], (unsigned int*)&mac[5]);
                        break;
                case 'p':
                        port = atoi(optarg);
                        break;
                default:
                        usage();
                        return -1;
                }
        }

        s = socket(AF_INET, SOCK_DGRAM, 0);

        if (s == -1) {
                perror("Opening socket");
                exit(EXIT_FAILURE);
        }

        memset(&address, 0, sizeof(struct sockaddr_in));
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = inet_addr(dstip);
        address.sin_port = htons(port);

        fill_magic_buf(magic_buf, mac);

        /* ten packets. TODO: use ping to check whether the destination is on or else. */
        while (packet_num-- > 0) {
                if (sendto(s, magic_buf, sizeof(magic_buf), 0,
                     (struct sockaddr *)&address, sizeof(address)) < 0) {
                        printf("sendto\n");
                        exit(EXIT_FAILURE);
                }
                sleep(1);
        }

        exit(EXIT_SUCCESS);
}

注意事项

1: 如果电脑非正常关机(比如按Power键来强制关机)还是无法WOL的,只有正常关机后,网卡仍会处于活动状态可以接收网络数据

2:如果是外网IP访问,路由器做端口映射必须要是顶级接入的路由器,因为二级以下NAT只能在是内网映射

3:Linux设置wol的时候,重启后enp3s0的设置又恢复Wake-on: d 状态, 写个脚本让开机执行 (Ubuntu18.04已修复)

在~/.profile 文件中添加

.config/wol.sh

wol.sh

#!/bin/sh
echo "12345678" | sudo -S ethtool -s enp3s0 wol g

-S表示bash脚本免输密码,参考:bash脚本,自动输入sudo的密码

关于 MAC 地址的扫描获取,这里只说一下思路,详细请查阅代码。第一种方式,也是我最开始想到的方式,使用 Ping 来 Ping 整个网段。开了四个线程,1-255大概需要30多秒,稍微有点慢,而且 .NET 的 Ping 类在 Android 上无法限制秒数。第二种方式,百度到的,直接向整个网段发送 UDP 消息,2秒解决战斗。扫描完成后获取 ARP 表就行。

下面给出的是发送幻数据包的方法:

publicstaticasyncvoidWake( stringbroadcast, intport, byte[] mac )
{
    using(UdpClient udp = newUdpClient)
    {
    udp.EnableBroadcast = true;
    byte[] packet = newbyte[ 6+ 16* 6];
    for( inti = 0; i < 6; i++)
    {
        packet[i] = 0xFF;
    }
    for( inti = 0; i < 16; i++)
    {
        for( intj = 0; j < 6; j++)
        {
            packet[ 6+ i * 6+ j] = mac[j];
        }
    }
    awaitudp.SendAsync(packet, packet.Length, broadcast, port);
    }
}

 

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门