Wake on Lan原理及Python实现

Last Updated: 2023-06-18 06:25:00 Sunday

-- TOC --

远程开机已经存在很多年了。从Intel和IBM在1997年公布标准以来,现在几乎所有的主板都支持网络开机。经过合适的设计,我们可以通过局域网或者广域网上开启另一台电脑,甚至用手机开启电脑!

网络开机叫做Wake-on-LAN,缩写是WoL。过程很简单,即通过发送一组特殊格式的网络封包(Magic Packet)给具有某个MAC地址的电脑,让该电脑从睡眠模式甚至是关机模式苏醒,即从ACPI的Sx(S3,S4,S5)模式返回S0运行模式。

前提:关机状态电脑的网口还在工作,灯还在闪,收到magic packet后,触发开机!能够被wake起来的电脑,在关机后,网口的灯一定是亮着的!

Magic Packet示例如下,是个能用的:

magic_packet

再来一个图,说明了上图最后的6个全0的byte的含义:

magic_packet_passwd.png

“Magic Packet”格式非世界标准,但被很多网卡制造商支持.

下面是一段现成的代码,这段代码中组装的magic packet,与上图稍微有点不一样。而且,图片上使用port 7,下面代码使用port 9。

#!/usr/bin/env python
import argparse
import socket
import struct
import logging
import time


LOG = logging.getLogger(__name__)


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, broadcast='255.255.255.255',
                      port=9, send_times=3):
    """
    Wake up computers having any of the given mac addresses.
    Wake on lan must be enabled on the host device.

    :param macs: a list of machines's macaddress to wake.
    :param broadcast: the ip address of the host to send the magic packet
                     to (default "255.255.255.255")
    :param port: the port of the host to send the magic packet to
               (default 9)
    :param kwargs:
    :return:
    """
    packets = []
    for mac in macs:
        packet = create_magic_packet(mac)
        packets.append(packet)

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    except BaseException as e:
        LOG.error("Wake up, Create sock failed: {err}".format(err=str(e)))
        # print("Wake up, Create sock failed: {err}".format(err=str(e)))
        return False

    ret = True
    try:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        for idx,packet in enumerate(packets):
            if idx % 10 == 0:
                time.sleep(0.1)
            for i in range(send_times):
                sock.sendto(packet, (broadcast, port))
    except BaseException as e:
        LOG.error("Wake up failed: {err}".format(err=str(e)))
        # print("Wake up failed: {err}".format(err=str(e)))
        ret = False
    finally:
        sock.close()

    return ret


BROADCAST_IP = '255.255.255.255'
DEFAULT_PORT = 9
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, broadcast=args.i, port=args.p)


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

这种用udp socket的方法,貌似只能通过发送局域网广播报文来实现wake on lan,如果是单播报文,这种方式没法控制二层报文的mac地址,报文都发不出去!

开启wake on lan功能,需要设置BIOS/UEFI!还可能需要在OS中配置网卡驱动。

遇到一个case,HP的电脑,Intel网卡,在Intel网卡驱动的属性中,找到电源管理,勾选只允许魔包唤醒(这个tab的其它两个选项默认都是有√的)。

下图是用上面的代码发出的magic packet,目的MAC地址是全F:

magic packet

如果是定向广播地址(网段广播),二层报文的目的mac也是全F!

跨网段呢?

  1. 通过网段内的设备,在网段内发广播;
  2. 网上看到,通过在路由器上做arp绑定(路由器收到数据,通过端口转发到相应的IP地址,192.168.1.100和端口9,告诉这台机器可以唤醒,ARP绑定必须存在,这个是很多无法远程唤醒的关键所在)。如果要发送单播唤醒报文,就需要在交换机上配置静态arp,可以使用单播唤醒吗?没意义,见如下分析!

如果跨了公网,要唤醒的电脑的IP是在某个私网内,就要具体问题具体分析了,反正目标是把magic packet发到电脑的网卡上!

发单播唤醒报文是没啥意义的,交换机的2层广播是肯定有的,没有广播,arp和dhcp都不能正常工作。交换机在查找mac转发表的时候,如果找不到,自己就会在所有端口上广播此frame(Mac转发)。

驱动支持

网卡驱动在系统关闭的时候,要保留WOL功能开启,不能彻底把网卡的电断了。如果关机状态下,网卡不闪烁,肯定不能唤醒。

本文链接:https://cs.pynote.net/net/202202211/

-- EOF --

-- MORE --