From 48dbff44ce52e1b04418f128aa4590225fa37b8e Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 25 Nov 2019 18:12:27 +0100 Subject: Initial commit --- .gitignore | 3 + Makefile | 2 + jawol.c | 461 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 466 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 jawol.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62e822a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +*~ +jawol diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7e04cba --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: + gcc -D_GNU_SOURCE -std=c99 -Os -g -Wall -Wextra -pedantic -o jawol jawol.c 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 ] [-p aa:bb:cc:dd[:ee:ff]] \\\n" +" 00:11:22:33:44:55 [, 11:22:33:44:55:66 [, ...]]\n" +" %s -d [-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} + -- cgit v1.2.3-55-g7522