去年12月搭建了guacamole实现了网页方式rdp,rdp对象是跑在虚拟机里的win10,最近发现如果用物理机当rdp client时,如果物理机休眠了,rdp就不可能起作用了。
有些网友说win10支持局域网读写访问时自动唤醒机器,具体方法搜了下,一堆不知所云的东西,主板bios里也翻了一遍,调整了几个参数也没有效果
偶然发现,我这块主板(ga h110tn-e)居然不支持WOL发魔术包的方式唤醒,必须用ether-wake包,这点从google paly上下载的wake-on-lan apk和openwrt上的luci app上都得到了证实
既然是这样,只能曲线救国了,rdp之前先想办法局域网唤醒
查看了guacamole文档,是支持wol的,这样就省了不少事,配置相关参数后,不出意外,wol无法唤醒我这块主板,配置文件如下:
下一步就是为guacamole添加ether-wake功能
参考了busybox中的代码,很容易就添加了ether-wake函数,代码如下:
guacamole-server\src\libguac\wol.c
#include <netinet/ether.h>
#include <linux/if.h>
#include <netpacket/packet.h>
#include <sys/ioctl.h>
/**
* Generate the WoL ether wake packet for the specified MAC address
* and place it in the character array.
*
* @param packet
* The character array that will contain the generated packet.
*
* @param mac_address
* The unsigned int representation of the MAC address to place in the packet.
*/
static void __guac_ether_wake_create_magic_packet(unsigned char packet[],
unsigned int mac_address[]) {
int i;
unsigned char mac[6];
/* Concurrently fill the first part of the packet with 0xFF, and copy the
MAC address from the int array to the char array. */
for (i = 0; i < 6; i++) {
mac[i] = mac_address[i];
}
/* Copy the MAC address contents into the char array that is storing
the first 12 byte packet. */
for (i = 0; i < 2; i++) {
memcpy(&packet[i * 6], &mac, 6 * sizeof(unsigned char));
}
unsigned char* pkt = packet;
pkt += 12;
*pkt++ = 0x08; /* 12 */ /* Or 0x0806 for ARP, 0x8035 for RARP */
*pkt++ = 0x42; /* 13 */
memset(pkt, 0xff, 6); /* 14 */
for (i = 0; i < 16; ++i) {
pkt += 6;
memcpy(pkt, &mac, 6); /* 20,26,32,... */
}
}
static ssize_t __guac_ether_wake_send_packet(unsigned char packet[],const char *ifname) {
struct sockaddr_ll wol_dest;
int wol_socket;
/* Set up the socket */
wol_socket = socket(PF_PACKET, SOCK_RAW, 0);
/* If socket open fails, bail out. */
if (wol_socket < 0) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Failed to open socket to send WOL:ether-wake packet";
return 0;
}
memset(&wol_dest, 0, sizeof(wol_dest));
wol_dest.sll_family = AF_PACKET;
struct ifreq ifr;
strcpy(ifr.ifr_name, ifname);
ioctl(wol_socket, SIOCGIFINDEX, &ifr);
wol_dest.sll_ifindex = ifr.ifr_ifindex;
/* The manual page incorrectly claims the address must be filled.
We do so because the code may change to match the docs. */
wol_dest.sll_halen = ETH_ALEN;
memcpy(wol_dest.sll_addr, packet, ETH_ALEN);
/* Send the packet and return number of bytes sent. */
int bytes = sendto(wol_socket, packet, GUAC_ETHER_WAKE_PACKET_SIZE, 0,
(struct sockaddr*) &wol_dest, sizeof(wol_dest));
close(wol_socket);
return bytes;
}
int guac_ether_wake(const char* mac_addr, const char* if_name) {
unsigned char ether_wake_packet[GUAC_ETHER_WAKE_PACKET_SIZE];
unsigned int dest_mac[6];
/* Parse mac address and return with error if parsing fails. */
if (sscanf(mac_addr, "%x:%x:%x:%x:%x:%x",
&(dest_mac[0]), &(dest_mac[1]), &(dest_mac[2]),
&(dest_mac[3]), &(dest_mac[4]), &(dest_mac[5])) != 6) {
guac_error = GUAC_STATUS_INVALID_ARGUMENT;
guac_error_message = "Invalid argument for WOL:ether-wake MAC address";
return -1;
}
/* Generate the magic packet. */
__guac_ether_wake_create_magic_packet(ether_wake_packet, dest_mac);
/* Send the packet and record bytes sent. */
int bytes_sent = __guac_ether_wake_send_packet(ether_wake_packet, if_name);
/* Return 0 if bytes were sent, otherwise return an error. */
if (bytes_sent)
return 0;
return -1;
}
如上是核心的几个构造和发socke包函数,剩下的就是把guac_ether_wake函数添加到rdp.c中,这样在网页中点击rdp设备前就能优先发wol包唤醒设备了。
guac_ether_wake函数需要指定网卡名称,所以增加了一个rdp的配置参数,允许用户自定义网卡名字,效果如下
最后说明下,为了保证唤醒成功,没有移除原有的magic package,相当于ether-wake和magic package同时发送,确保唤醒成功