| 검색 | ?

1.1. Magic packet의 생성

  • Wake on LAN (WOL)을 UDP상에서 구현 하는 경우 UDP packet의 첫 6 byte는 FFH로 채워지고 이후 대상 PC의 Ethenet MAC address 6 byte가 16회 반복시켜서 UDP Magic packet을 만들수 있습니다. 만약 MAC address가 "44:87:FC:8F:BB:B4" 라고 한다면 다음과 같은 UDP packet을 만들어 Broadcast나 Unicast로 송출하면 됩니다.
    00000000 FF FF FF FF FF FF 44 87 | FC 8F BB B4 44 87 FC 8F [......D.....D...]
    00000010 BB B4 44 87 FC 8F BB B4 | 44 87 FC 8F BB B4 44 87 [..D.....D.....D.]
    00000020 FC 8F BB B4 44 87 FC 8F | BB B4 44 87 FC 8F BB B4 [....D.....D.....]
    00000030 44 87 FC 8F BB B4 44 87 | FC 8F BB B4 44 87 FC 8F [D.....D.....D...]
    00000040 BB B4 44 87 FC 8F BB B4 | 44 87 FC 8F BB B4 44 87 [..D.....D.....D.]
    00000050 FC 8F BB B4 44 87 FC 8F | BB B4 44 87 FC 8F BB B4 [....D.....D.....]
    00000060 44 87 FC 8F BB B4                                 [D.....          ]
    

1.2. Wake on LAN (WOL) packet을 수신하였는데도 켜지지 않는 경우

완벽하게 Wake on LAN (WOL)을 지원하는 경우는 상관없으나 메인보드로의 전원을 인가하는 방법에 따라서 일부 전원이 인가하여 1회 부팅후 커널등에 의해서 Wake on LAN (WOL)을 동작시킬수 있도록 매번 설정해야 동작하는 경우가 있습니다.

즉, 정전등에 의해서 완전히 전원이 소실된 경우 적어도 한번은 부팅후 정상적인 shutdown 절차를 밟아야만 Wake on LAN (WOL)이 동작하도록 설계된 H/W 가 많아서 이를 정확히 확인후 사용하시는게 좋습니다. 요즘 PC들은 대부분 이에 속합니다.

1.3. 예제소스

  • 예제 소스 : 아래의 예제는 여러개의 NIC를 가진 다중 IP환경에서에 대한 고려는 빠져있습니다. 실제 범용적인 구현을 위해서는 다중IP에 대한 bind 및 개별 IP별로 broadcast를 구현하는것이 좋다는 점 유의 하면서 보시면 좋을듯 합니다.
    /*
      Copyright (C) JAEHYUK CHO
      All rights reserved.
      Code by JaeHyuk Cho <mailto:minzkn@minzkn.com>
    */
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int s_argc, char **s_argv);
    
    int main(int s_argc, char **s_argv)
    {
        static const unsigned char s_target_mac[6] = { /* 이 부분을 자신이 깨우고자 하는 PC의 Ethernet MAC address로 채워주면 되겠습니다. */
            0x44u, 0x87u, 0xfcu, 0x8fu, 0xbbu, 0xb4u
        };
    
        int s_socket;
        struct sockaddr_in s_sockaddr_in;
        unsigned char s_magic_packet[ 6 + (6 * 16) ];
        int s_repeat;
        ssize_t s_send_bytes;
        int s_value;
    
        /* UDP socket open */
        s_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if(s_socket == (-1)) {
            perror("socket");
            return(EXIT_FAILURE);
        }
    
        /* bind */
        (void)memset((void *)(&s_sockaddr_in), 0, sizeof(s_sockaddr_in));
        s_sockaddr_in.sin_family = AF_INET;
        s_sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
        s_sockaddr_in.sin_port = htons(0);
        if(bind(s_socket, (const struct sockaddr *)(&s_sockaddr_in), (socklen_t)sizeof(s_sockaddr_in)) == (-1)) {
            perror("bind");
    
            (void)close(s_socket);
    
            return(EXIT_FAILURE);
        }
    
        /* broadcast socket option enable (Broadcast를 위해서는 SO_BROADCAST가 설정되어야 합니다.) */
        s_value = 1;
        (void)setsockopt(s_socket, SOL_SOCKET, SO_BROADCAST, (const void *)(&s_value), (socklen_t)sizeof(s_value));
    
        /* WOL packet build (repeat 6) */
        (void)memset((void *)(&s_magic_packet[0]), 0xff, (size_t)6u); /* 첫 6byte는 FFH로 채웁니다. */
        for(s_repeat = 0;s_repeat < 16;s_repeat++) { /* 깨우고자 하는 MAC address를 16회 반복하여 채웁니다. */
            (void)memcpy((void *)(&s_magic_packet[6 + (s_repeat * 6)]), (const void *)(&s_target_mac[0]), sizeof(s_target_mac));
        }
    
        /* broadcast socket address structure set */
        (void)memset((void *)(&s_sockaddr_in), 0, sizeof(s_sockaddr_in));
        s_sockaddr_in.sin_family = AF_INET;
        (void)inet_pton(s_sockaddr_in.sin_family, "255.255.255.255", (void *)(&s_sockaddr_in.sin_addr));
        s_sockaddr_in.sin_port = htons(2304); /* any port ... (사실상 port는 아무거나 상관하지 않습니다. 다만 다른 응용프로그램들에게 방해가 되지 않는 포트를 선택하는것 뿐입니다.) */
    
        /* send */
        s_send_bytes = sendto(
            s_socket,
            (const void *)(&s_magic_packet[0]),
            sizeof(s_magic_packet),
            0,
            (const struct sockaddr *)(&s_sockaddr_in),
            (socklen_t)sizeof(s_sockaddr_in)
        );
        if(s_send_bytes == ((ssize_t)(-1))) {
            perror("sendto");
    
            (void)close(s_socket);
    
            return(EXIT_FAILURE);
        }
    
        (void)fprintf(stdout,
            "WOL packet : %ld bytes (Target %02X:%02X:%02X:%02X:%02X:%02X)\n",
            (long)s_send_bytes,
            (unsigned int)s_target_mac[0], (unsigned int)s_target_mac[1], (unsigned int)s_target_mac[2],
            (unsigned int)s_target_mac[3], (unsigned int)s_target_mac[4], (unsigned int)s_target_mac[5]
        );
    
        /* socket close */
        (void)close(s_socket);
    
        return(EXIT_SUCCESS);
    }
    
    /* vim: set expandtab: */
    /* End of source */
    

1.4. 예) libvirt (KVM+QEMU) 로 구축된 가상 서버를 WOL 패킷을 수신하여 해당하는 MAC 주소의 Guest 기동해주는 스크립트

#!/bin/bash

# listen to udp port 2304 for packets, check if it is a magic packet
netcat -dkn -l 2304 -u | stdbuf -o0 xxd -c 6 -p | stdbuf -o0 uniq | stdbuf -o0 grep -v 'ffffffffffff' | while read
do
    echo "Got triggered with $REPLY"
    mac="${REPLY:0:2}:${REPLY:2:2}:${REPLY:4:2}:${REPLY:6:2}:${REPLY:8:2}:${REPLY:10:2}"

    # loop through libvirt machines
    for vm in $(virsh list --all --name)
    do
        # Get the MAC address and compare with the magic packet
        vmmac=$(virsh dumpxml $vm | grep "mac address" | awk -F\' '{ print $2}')
        if [ "$vmmac" = "$mac" ]
        then
            state=$(virsh list --all|awk -v vm=$vm '{ if ($2 == vm ) print $3 }')
            echo "Found $vm with $mac in $state state"

            # Dependent on the state, resume or start
            [ $state == "paused" ] && virsh -q resume $vm && virsh domtime --domain $vm --now
            [ $state == "shut" ] && virsh -q start $vm
        fi
    done
done


Copyright ⓒ MINZKN.COM
All Rights Reserved.