summaryrefslogblamecommitdiffstats
path: root/jawol.c
blob: d604455fdfa5a0738734d1c92f8c7039e4c4bca7 (plain) (tree)

























                                                                                 

                                                                            























                                                                               

                                                                               

















                             




                                                                                                      

























































































                                                                                        
                                           






































                                                                                                                
                                                 













































































                                                                                                          
                                           

















                                                                                                        
                                                 













































                                                                                                          
                                    











































































































                                                                                                  
/*
 * 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;
}