summaryrefslogtreecommitdiffstats
path: root/jawol.c
diff options
context:
space:
mode:
authorSimon Rettberg2019-11-25 18:12:27 +0100
committerSimon Rettberg2019-11-25 18:12:27 +0100
commit48dbff44ce52e1b04418f128aa4590225fa37b8e (patch)
tree59dacff6d8cacbcf892972bf5fcc1d8d70562f7e /jawol.c
downloadjawol-48dbff44ce52e1b04418f128aa4590225fa37b8e.tar.gz
jawol-48dbff44ce52e1b04418f128aa4590225fa37b8e.tar.xz
jawol-48dbff44ce52e1b04418f128aa4590225fa37b8e.zip
Initial commit
Diffstat (limited to 'jawol.c')
-rw-r--r--jawol.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/jawol.c b/jawol.c
new file mode 100644
index 0000000..7f3f88e
--- /dev/null
+++ b/jawol.c
@@ -0,0 +1,461 @@
+/*
+ * 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"
+"\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\n"
+"raw mode options (-b, -i) will be ignored in this mode.\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>
+
+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[200];
+ 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;
+ }
+ 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[200];
+ 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;
+ }
+ 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;
+ }
+ if ( offset > 180 ) {
+ fprintf( stderr, "Packet buffer overflow.\n" );
+ abort();
+ }
+ if (options.debug) {
+ printf("Packet is ");
+ for (i = 0; i < offset; i++)
+ printf(" %2.2x", pkt[i]);
+ printf(".\n");
+ }
+ 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;
+}
+