blob: d6167d05d41fbb2771ff8ad106268632998a6aa6 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "guacamole/error.h"
#include "guacamole/tcp.h"
#include "guacamole/timestamp.h"
#include "guacamole/wol.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
/**
* Generate the magic Wake-on-LAN (WoL) 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_wol_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++) {
packet[i] = 0xFF;
mac[i] = mac_address[i];
}
/* Copy the MAC address contents into the char array that is storing
the rest of the packet. */
for (i = 1; i <= 16; i++) {
memcpy(&packet[i * 6], &mac, 6 * sizeof(unsigned char));
}
}
/**
* Converts the given IPv4 address and UDP port number into its binary
* representation (struct sockaddr_in). The size of the structure used will be
* stored in wol_dest_size.
*
* @param addr
* The IPv4 address to convert.
*
* @param udp_port
* The UDP port number to convert and include in the resulting sockaddr_in
* structure.
*
* @param wol_dest
* Storage that is sufficiently large and correctly aligned such that it
* may be typecast as a sockaddr_in without issue (this is guaranteed by
* the definition of the sockaddr_storage structure).
*
* @param wol_dest_size
* Pointer to a socklen_t that should receive the size of the sockaddr_in
* structure. This value is assigned only if this call succeeds.
*
* @return
* A positive value if conversion succeeded, zero if conversion failed, and
* a negative value if IPv4 is unsupported by this system.
*/
static int __guac_wol_convert_addr_ipv4(const char* addr, const unsigned short udp_port,
struct sockaddr_storage* wol_dest, socklen_t* wol_dest_size) {
struct sockaddr_in* wol_dest_ipv4 = (struct sockaddr_in*) wol_dest;
*wol_dest_size = sizeof(struct sockaddr_in);
/* Init address structure for IPv4 details */
memset(wol_dest, 0, *wol_dest_size);
wol_dest_ipv4->sin_port = htons(udp_port);
wol_dest_ipv4->sin_family = AF_INET;
return inet_pton(wol_dest_ipv4->sin_family, addr, &(wol_dest_ipv4->sin_addr));
}
/**
* Converts the given IPv6 address and UDP port number into its binary
* representation (struct sockaddr_in6). The size of the structure used will be
* stored in wol_dest_size.
*
* @param addr
* The IPv6 address to convert.
*
* @param udp_port
* The UDP port number to convert and include in the resulting sockaddr_in6
* structure.
*
* @param wol_dest
* Storage that is sufficiently large and correctly aligned such that it
* may be typecast as a sockaddr_in6 without issue (this is guaranteed by
* the definition of the sockaddr_storage structure).
*
* @param wol_dest_size
* Pointer to a socklen_t that should receive the size of the sockaddr_in6
* structure. This value is assigned only if this call succeeds.
*
* @return
* A positive value if conversion succeeded, zero if conversion failed, and
* a negative value if IPv6 is unsupported by this system.
*/
static int __guac_wol_convert_addr_ipv6(const char* addr, const unsigned short udp_port,
struct sockaddr_storage* wol_dest, socklen_t* wol_dest_size) {
struct sockaddr_in6* wol_dest_ipv6 = (struct sockaddr_in6*) wol_dest;
*wol_dest_size = sizeof(struct sockaddr_in6);
/* Init address structure for IPv6 details */
memset(wol_dest, 0, *wol_dest_size);
wol_dest_ipv6->sin6_family = AF_INET6;
wol_dest_ipv6->sin6_port = htons(udp_port);
return inet_pton(wol_dest_ipv6->sin6_family, addr, &(wol_dest_ipv6->sin6_addr));
}
/**
* Converts the given IPv4 or IPv6 address and UDP port number into its binary
* representation (either a struct sockaddr_in or struct sockaddr_in6). The
* size of the structure used will be stored in wol_dest_size. If conversion
* fails, guac_error and guac_error_message are set appropriately.
*
* @param addr
* The address to convert.
*
* @param udp_port
* The UDP port number to convert and include in the resulting structure.
*
* @param wol_dest
* Storage that is sufficiently large and correctly aligned such that it
* may be typecast as a sockaddr_in OR sockaddr_in6 without issue (this is
* guaranteed by the definition of the sockaddr_storage structure).
*
* @param wol_dest_size
* Pointer to a socklen_t that should receive the size of the structure
* ultimately used. This value is assigned only if this call succeeds.
*
* @return
* Non-zero if conversion succeeded, zero otherwise.
*/
static int __guac_wol_convert_addr(const char* addr, const unsigned short udp_port,
struct sockaddr_storage* wol_dest, socklen_t* wol_dest_size) {
/* Attempt to resolve as an IPv4 address first */
int ipv4_retval = __guac_wol_convert_addr_ipv4(addr, udp_port, wol_dest, wol_dest_size);
if (ipv4_retval > 0)
return 1;
/* Failing that, reattempt as IPv6 */
int ipv6_retval = __guac_wol_convert_addr_ipv6(addr, udp_port, wol_dest, wol_dest_size);
if (ipv6_retval > 0)
return 1;
/*
* Translate all possible IPv4 / IPv6 failure combinations into a single,
* human-readable message.
*/
/* The provided address is not valid IPv4 ... */
if (ipv4_retval == 0) {
/* ... and also not valid IPv6. */
if (ipv6_retval == 0) {
guac_error = GUAC_STATUS_INVALID_ARGUMENT;
guac_error_message = "The broadcast or multicast address "
"specified for Wake-on-LAN is not a valid IPv4 or IPv6 "
"address";
}
/* ... but we can't try IPv6 because this system doesn't support it. */
else {
guac_error = GUAC_STATUS_INVALID_ARGUMENT;
guac_error_message = "IPv6 is not supported by this system and "
"the broadcast/multicast address specified for Wake-on-LAN is "
"not a valid IPv4 address";
}
}
/* This system bizarrely doesn't support IPv4 ... */
else {
/* ... and the provided address is not valid IPv6. */
if (ipv6_retval == 0) {
guac_error = GUAC_STATUS_INVALID_ARGUMENT;
guac_error_message = "IPv4 is not supported by this system and "
"the broadcast/multicast address specified for Wake-on-LAN is "
"not a valid IPv6 address";
}
/* ... nor IPv6 (should be impossible). */
else {
guac_error = GUAC_STATUS_NOT_SUPPORTED;
guac_error_message = "Neither IPv4 nor IPv6 is supported by this "
"system (this should not be possible)";
}
}
return 0;
}
/**
* Send the magic Wake-on-LAN (WoL) packet to the specified broadcast address,
* returning the number of bytes sent, or zero if any error occurred and nothing
* was sent.
*
* @param broadcast_addr
* The broadcast address to which to send the magic WoL packet.
*
* @param udp_port
* The UDP port to use when sending the WoL packet.
*
* @param packet
* The magic WoL packet to send.
*
* @return
* The number of bytes sent, or zero if nothing could be sent.
*/
static ssize_t __guac_wol_send_packet(const char* broadcast_addr,
const unsigned short udp_port, unsigned char packet[]) {
struct sockaddr_storage wol_dest;
socklen_t wol_dest_size;
/* Resolve broadcast address */
if (!__guac_wol_convert_addr(broadcast_addr, udp_port, &wol_dest, &wol_dest_size))
return 0;
/* Set up the socket */
int wol_socket = socket(wol_dest.ss_family, SOCK_DGRAM, 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 Wake-on-LAN packet";
return 0;
}
/* Set up socket for IPv4 broadcast. */
if (wol_dest.ss_family == AF_INET) {
/* For configuring socket broadcast */
int wol_bcast = 1;
/* Attempt to set IPv4 broadcast; exit with error if this fails. */
if (setsockopt(wol_socket, SOL_SOCKET, SO_BROADCAST, &wol_bcast,
sizeof(wol_bcast)) < 0) {
close(wol_socket);
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Failed to set IPv4 broadcast for Wake-on-LAN socket";
return 0;
}
}
/* Set up socket for IPv6 multicast. */
else {
/* Stick to a single hop for now. */
int hops = 1;
/* Attempt to set IPv6 multicast; exit with error if this fails. */
if (setsockopt(wol_socket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,
sizeof(hops)) < 0) {
close(wol_socket);
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Failed to set IPv6 multicast for Wake-on-LAN socket";
return 0;
}
}
/* Send the packet and return number of bytes sent. */
ssize_t bytes;
GUAC_RETRY_EINTR(bytes, sendto(wol_socket, packet, GUAC_WOL_PACKET_SIZE, 0,
(struct sockaddr*) &wol_dest, wol_dest_size));
close(wol_socket);
return bytes;
}
int guac_wol_wake(const char* mac_addr, const char* broadcast_addr,
const unsigned short udp_port) {
unsigned char wol_packet[GUAC_WOL_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 Wake-on-LAN MAC address";
return -1;
}
/* Generate the magic packet. */
__guac_wol_create_magic_packet(wol_packet, dest_mac);
/* Send the packet and record bytes sent. */
int bytes_sent = __guac_wol_send_packet(broadcast_addr, udp_port,
wol_packet);
/* Return 0 if bytes were sent, otherwise return an error. */
if (bytes_sent)
return 0;
return -1;
}
int guac_wol_wake_and_wait(const char* mac_addr, const char* broadcast_addr,
const unsigned short udp_port, int wait_time, int retries,
const char* hostname, const char* port, const int timeout) {
/* Attempt to connect, first. */
int sockfd = guac_tcp_connect(hostname, port, timeout);
/* If connection succeeds, no need to wake the system. */
if (sockfd > 0) {
close(sockfd);
return 0;
}
/* Close the fd to avoid resource leak. */
close(sockfd);
/* Send the magic WOL packet and store return value. */
int retval = guac_wol_wake(mac_addr, broadcast_addr, udp_port);
/* If sending WOL packet fails, just return the received return value. */
if (retval)
return retval;
/* Try to connect on the specified TCP port and hostname or IP. */
for (int i = 0; i < retries; i++) {
sockfd = guac_tcp_connect(hostname, port, timeout);
/* Connection succeeded - close socket and exit. */
if (sockfd > 0) {
close(sockfd);
return 0;
}
/**
* Connection did not succed - close the socket and sleep for the
* specified amount of time before retrying.
*/
close(sockfd);
guac_timestamp_msleep(wait_time * 1000);
}
/* Failed to connect, set error message and return an error. */
guac_error = GUAC_STATUS_REFUSED;
guac_error_message = "Unable to connect to remote host.";
return -1;
}