/*
* Quick copy paste combo of
* wolc: https://github.com/timofurrer/WOL
* ether-wake.c: v1.09 11/12/2003 Donald Becker, http://www.scyld.com/
* so we can have one tool that supports RAW mode (ethernet frame type 0x0842)
* and encapsulated UDP mode (like the wol or wakeonlan cmdline utils do).
*
* GPL2
*/
static const char usage_msg[] =
"Usage:\n"
" %s [-b] [-i <ifname>] [-p aa:bb:cc:dd[:ee:ff]] \\\n"
" 00:11:22:33:44:55 [, 11:22:33:44:55:66 [, ...]]\n"
" %s -d <dest_ip> [-p aa:bb:cc:dd[:ee:ff]] \\\n"
" 00:11:22:33:44:55 [, 11:22:33:44:55:66 [, ...]]\n"
"\n"
"The first variant sends a raw ethernet frame with type 0x0842 (WoL) to the\n"
"specified destination(s). In this mode, the source interface must be known\n"
"(-i), which will default to eth0 if omitted.\n"
"\n"
"The second variant sends the UDP version of a WoL packet to port 9 of the\n"
"given destination(s). This required the destination parameter (-d) to be set\n"
"to either the limited broadcast address, or the directed broadcast address of\n"
"the destination network, i.e. 10.1.2.255 if the destination host is\n"
"10.1.2.3/24.\n"
"This requires the final router connected to the destination subnet to be\n"
"configured accordingly.\n"
"\n"
"Options for raw mode (etherwake):\n"
" --broadcast\n"
" -b Send wake-up packet to the broadcast MAC-address, instead of\n"
" destination MAC address.\n"
" --interface IFNAME\n"
" -i IFNAME Use interface IFNAME instead of the default 'eth0'.\n"
"\n"
"Options for UDP mode (wakeonlan):\n"
" --destination IPADDR\n"
" -d IPADDR Destination (broadcast) IP address.\n"
"\n"
"Common options:\n"
" --verbose\n"
" -v Verbose mode, print some additional information.\n"
" --debug\n"
" -D Increase the debug level, print much more information.\n"
" --password PASSWD\n"
" -p PASSWD Append the four or six byte password PW to the packet.\n"
" A password is only required for a few adapter types.\n"
" The password may be specified in ethernet hex format\n"
" or dotted decimal (Internet address),\n"
" f.i. 00:22:44:66:88:aa or 192.168.1.1\n"
"\n"
"UDP mode will be selected if -d is passed on the command line. Any raw mode\n"
"options (-b, -i) won't work in this mode so program execution is aborted.\n"
;
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netpacket/packet.h>
#include <netinet/ether.h>
#include <net/ethernet.h>
#include <linux/if.h>
#include <sys/ioctl.h>
#include <getopt.h>
#define PACKET_SIZE (200)
#define CHECK_PACKET_SIZE(x) do { \
if ( (x) >= PACKET_SIZE ) { fprintf( stderr, "Packet size overflow.\n" ); abort(); } \
} while(0)
static struct {
const char *ifname;
struct in_addr dest;
char wolpw[6];
int wolpwlen;
bool broadcast;
bool verbose; // Spam
bool debug; // Even more spam
} options;
static int parse_errors = 0;
static int get_dest_addr(const char *arg, struct ether_addr *dest_ether);
static int get_fill(unsigned char *pkt, const uint8_t *dest_ether, bool with_header);
static void get_wol_pw(const char *optarg);
static int get_source_addr(int s, const char *ifname, unsigned char *output);
static struct in_addr get_ip(const char *arg);
static int rawsocket();
static int udpsocket();
static int etherwake(int count, char **macs);
static int wakeonlan(int count, char **maclist);
static void print_usage(const char *a0)
{
printf( usage_msg, a0, a0 );
}
int main(int argc, char **argv)
{
int opt;
bool gotip = false, gotif = false;
static const char *optString = "bDi:p:vhd:";
static const struct option longOpts[] = {
{ "broadcast", no_argument, NULL, 'b' },
{ "debug", no_argument, NULL, 'D' },
{ "interface", required_argument, NULL, 'i' },
{ "password", required_argument, NULL, 'p' },
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ "destination", required_argument, NULL, 'd' },
{ 0, 0, 0, 0 }
};
// Conveinience: Support MAC addr with dashes
for ( int i = 1; i < argc; ++i ) {
int j = 0;
while ( argv[i][j] != '\0' ) {
if ( argv[i][j] == '-' && j > 1 ) argv[i][j] = ':';
j++;
}
}
while ( ( opt = getopt_long( argc, argv, optString, longOpts, NULL ) ) != -1 ) {
switch ( opt ) {
case 'b': options.broadcast = true; gotif = true; break;
case 'D': options.debug = true; break;
case 'i': options.ifname = optarg; gotif = true; break;
case 'p': get_wol_pw( optarg ); break;
case 'v': options.verbose = true; break;
case 'h': print_usage( argv[0] ); return 0;
case 'd': options.dest = get_ip( optarg ); gotip = true; break;
case '?': parse_errors++; break;
}
}
if ( gotip && gotif ) {
fprintf( stderr, "Cannot use -d and -b/-i at the same time.\n" );
parse_errors++;
}
if ( parse_errors == 0 && optind >= argc ) {
fprintf( stderr, "No MAC address(es) given.\n" );
parse_errors++;
}
if ( parse_errors != 0 ) {
fprintf( stderr, "Try %s --help.\n", argv[0] );
}
// Seems we're somewhat prepared to go
if ( !gotip ) {
// raw (etherwake) mode
if ( options.verbose ) {
printf( "Using RAW mode.\n" );
}
return etherwake( argc - optind, argv + optind );
}
// UDP (wakeonlan) mode
if ( options.verbose ) {
printf( "Using UDP mode.\n" );
}
return wakeonlan( argc - optind, argv + optind );
}
static int etherwake(int count, char **maclist)
{
unsigned char outpack[PACKET_SIZE];
int s = rawsocket();
if ( s == -1 )
return 2;
if ( options.ifname == NULL ) {
options.ifname = "eth0";
}
#if defined(PF_PACKET)
struct sockaddr_ll whereto = {0};
do {
struct ifreq ifr;
snprintf( ifr.ifr_name, IFNAMSIZ, "%s", options.ifname );
if (ioctl(s, SIOCGIFINDEX, &ifr) == -1) {
perror( "ioctl SIOCGIFINDEX on raw socket" );
fprintf( stderr, "Are you sure %s exists?\n", options.ifname );
return 3;
}
whereto.sll_family = AF_PACKET;
whereto.sll_ifindex = ifr.ifr_ifindex;
} while (0);
#else
struct sockaddr whereto = {0}; /* who to wake up */
#endif
if ( get_source_addr( s, options.ifname, outpack + 6 ) != 0 ) {
fprintf( stderr, "Cannot determine source MAC address for outgoing packet, using bogus one\n" );
outpack[6] = 2;
outpack[7] = outpack[8] = 34;
outpack[9] = outpack[10] = outpack[11] = 56;
}
struct ether_addr dest_ether;
for ( int idx = 0; idx < count; ++idx ) {
if ( get_dest_addr( maclist[idx], &dest_ether ) != 0 ) {
fprintf( stderr, "Cannot parse mac address '%s', skipping...\n", maclist[idx] );
continue;
}
int packet_size = get_fill( outpack, dest_ether.ether_addr_octet, true );
if ( options.wolpwlen > 0 ) {
memcpy( outpack + packet_size, options.wolpw, options.wolpwlen );
packet_size += options.wolpwlen;
}
CHECK_PACKET_SIZE( packet_size );
if ( options.debug ) {
printf( "The final packet for %s is:", maclist[idx] );
for ( int i = 0; i < packet_size; i++ ) printf( " %2.2x", outpack[i] );
printf( ".\n" );
}
#if defined(PF_PACKET)
/* The manual page incorrectly claims the address must be filled.
We do so because the code may change to match the docs. */
whereto.sll_halen = ETH_ALEN;
memcpy(whereto.sll_addr, outpack, ETH_ALEN);
#else
whereto.sa_family = 0;
snprintf( whereto.sa_data, sizeof(whereto.sa_data), "%s", options.ifname );
#endif
printf( "Sending magic packet to %s...\n", maclist[idx] );
int bytes = sendto( s, outpack, packet_size, 0, (struct sockaddr *)&whereto,
sizeof(whereto) );
if ( bytes == -1 ) {
perror( "sendto" );
} else if ( bytes != packet_size ) {
fprintf( stderr, "Packet truncated, sent %d of %d bytes!\n", bytes, packet_size );
} else if ( options.debug ) {
printf( "Sendto worked! %d bytes.\n", bytes );
}
}
close( s );
return 0;
}
static int rawsocket()
{
int s;
int one = 1;
/* Note: PF_INET, SOCK_DGRAM, IPPROTO_UDP would allow SIOCGIFHWADDR to
work as non-root, but we need SOCK_PACKET to specify the Ethernet
destination address. */
#if defined(PF_PACKET)
s = socket(PF_PACKET, SOCK_RAW, 0);
#else
s = socket(AF_INET, SOCK_PACKET, SOCK_PACKET);
#endif
if (s == -1) {
if (errno == EPERM) {
fprintf(stderr, "This program must be run as root for raw mode to work.\n");
} else {
perror("socket");
}
return -1;
}
/* This is necessary for broadcasts to work */
if ( setsockopt( s, SOL_SOCKET, SO_BROADCAST, (char *)&one, sizeof(one) ) == -1 ) {
perror( "setsockopt: SO_BROADCAST" );
return -1;
}
return s;
}
int udpsocket()
{
int sock;
int optval = 1;
sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
if ( sock == -1 ) {
perror( "socket" );
return -1;
}
if ( setsockopt( sock, SOL_SOCKET, SO_BROADCAST, (char *) &optval, sizeof( optval ) ) == -1 ) {
perror( "setsockopt: SO_BROADCAST" );
return -1;
}
return sock;
}
static int wakeonlan(int count, char **maclist)
{
struct ether_addr dest_ether;
unsigned char outpack[PACKET_SIZE];
int sock = udpsocket();
if ( sock == -1 )
return 2;
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons( 9 ),
.sin_addr = options.dest,
};
for ( int idx = 0; idx < count; ++idx ) {
if ( get_dest_addr( maclist[idx], &dest_ether ) != 0 ) {
fprintf( stderr, "Cannot parse mac address '%s', skipping...\n", maclist[idx] );
continue;
}
int packet_size = get_fill( outpack, dest_ether.ether_addr_octet, false );
if ( options.wolpwlen > 0 ) {
memcpy( outpack + packet_size, options.wolpw, options.wolpwlen );
packet_size += options.wolpwlen;
}
CHECK_PACKET_SIZE( packet_size );
if ( options.debug ) {
printf( "The final packet for %s is:", maclist[idx] );
for ( int i = 0; i < packet_size; i++ ) printf( " %2.2x", outpack[i] );
printf( ".\n" );
}
printf( "Sending magic packet to %s...\n", maclist[idx] );
int bytes = sendto( sock, outpack, packet_size, 0, (struct sockaddr *)&addr,
sizeof(addr) );
if ( bytes == -1 ) {
perror( "sendto" );
} else if ( bytes != packet_size ) {
fprintf( stderr, "Packet truncated, sent %d of %d bytes!\n", bytes, packet_size );
} else if ( options.debug ) {
printf( "Sendto worked! %d bytes.\n", bytes );
}
}
close( sock );
return 0;
}
static int get_fill(unsigned char *pkt, const uint8_t *bindestmac, bool with_header)
{
int offset = 0;
int i;
if ( with_header ) {
if ( options.broadcast ) {
memset( pkt, 0xff, 6 );
} else {
memcpy( pkt, bindestmac, 6 );
}
// Leave byte 6-11 alone, already set to source MAC addr
// Next, type
pkt[12] = 0x08;
pkt[13] = 0x42;
offset = 14;
}
// Write sync bytes payload
memset(pkt+offset, 0xff, 6);
// Repeat destination address 16 times
offset += 6;
for (i = 0; i < 16; i++) {
memcpy(pkt + offset, bindestmac, 6);
offset += 6;
}
CHECK_PACKET_SIZE( offset );
return offset;
}
static void get_wol_pw(const char *optarg)
{
unsigned int passwd[6];
int byte_cnt;
int i;
byte_cnt = sscanf(optarg, "%2x:%2x:%2x:%2x:%2x:%2x",
&passwd[0], &passwd[1], &passwd[2],
&passwd[3], &passwd[4], &passwd[5]);
if (byte_cnt < 4) { // Support 4 chars in this notion
byte_cnt = sscanf(optarg, "%u.%u.%u.%u",
&passwd[0], &passwd[1], &passwd[2], &passwd[3]);
}
if (byte_cnt < 4) {
fprintf(stderr, "Unable to read the Wake-On-LAN password.\n");
parse_errors++;
return;
}
if (byte_cnt > 6) {
fprintf(stderr, "The impossible happened.\n");
abort();
}
printf(" The Magic packet password is %2.2x %2.2x %2.2x %2.2x (%d).\n",
passwd[0], passwd[1], passwd[2], passwd[3], byte_cnt);
options.wolpwlen = byte_cnt;
for (i = 0; i < byte_cnt; i++) {
options.wolpw[i] = passwd[i];
}
}
/**
* Get MAC address of given interface (binary 6 byte)
* returns 0 on success
*/
static int get_source_addr(int s, const char *ifname, unsigned char *output)
{
/* Fill in the source address, if possible.
The code to retrieve the local station address is Linux specific. */
struct ifreq if_hwaddr;
snprintf(if_hwaddr.ifr_name, IFNAMSIZ, "%s", ifname);
if (ioctl(s, SIOCGIFHWADDR, &if_hwaddr) < 0) {
fprintf(stderr, "SIOCGIFHWADDR on %s failed: %s\n", ifname,
strerror(errno));
/* Magic packets still work if our source address is bogus, but
we fail just to be anal. */
return 1;
}
memcpy(output, if_hwaddr.ifr_hwaddr.sa_data, 6);
if ( options.verbose ) {
unsigned char *hwaddr = (unsigned char*)if_hwaddr.ifr_hwaddr.sa_data;
printf("The hardware address (SIOCGIFHWADDR) of %s is type %d "
"%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x.\n", ifname,
if_hwaddr.ifr_hwaddr.sa_family, hwaddr[0], hwaddr[1],
hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
}
return 0;
}
/**
* Convert the host ID string to a MAC address.
* The string may be a
* Host name
* IP address string
* MAC address string
*/
static int get_dest_addr(const char *hostid, struct ether_addr *dest_ether)
{
struct ether_addr *eap;
eap = ether_aton(hostid);
if (eap != NULL) {
*dest_ether = *eap;
if (options.debug) {
printf("The target station address is %s.\n",
ether_ntoa(dest_ether));
}
} else if (ether_hostton(hostid, dest_ether) == 0) {
if (options.debug) {
printf("Station address for hostname %s is %s.\n",
hostid, ether_ntoa(dest_ether));
}
} else {
fprintf(stderr,
"ether-wake: The Magic Packet host address must be "
"specified as\n"
" - a station address, 00:11:22:33:44:55, or\n"
" - a station address, 00-11-22-33-44-55, or\n"
" - a hostname with a known 'ethers' entry.\n");
return -1;
}
return 0;
}
static struct in_addr get_ip(const char *arg)
{
struct in_addr tmp;
if ( inet_aton( arg, &tmp ) == 0 ) {
fprintf( stderr, "Converting destination address via inet_aton failed.\n" );
parse_errors++;
}
return tmp;
}