Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 66 additions & 39 deletions wakeonlan
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
######################################################################

use strict;
use Socket;
use IO::Socket::IP;
use Socket qw(IPPROTO_UDP :addrinfo);
use Socket6 qw(inet_ntop);
use Getopt::Long;
use Pod::Usage;

Expand Down Expand Up @@ -33,7 +35,7 @@ our @hwaddr_regexs = (
'^[\da-f]{12}$',
);

my $DEFAULT_IP = '255.255.255.255';
my $DEFAULT_TARGET = '255.255.255.255';
my $DEFAULT_PORT = getservbyname('discard', 'udp');

my $verbose = 1;
Expand All @@ -47,6 +49,7 @@ my %stats = (
valid => 0,
invalid => 0,
sent => 0,
failed => 0,
);

######################################################################
Expand All @@ -58,14 +61,12 @@ sub isValidPort {
}


sub isValidIPAddress {
my $ipaddress = shift;
my $t = 0;
sub isValidTarget {
my $target = shift;

$t += $_ for map { ($_ <= 255)?1:0 }
$ipaddress =~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
# TODO: check if target is a valid domain, IPv4 address or IPv6 address

return ($t == 4) ? 1 : 0;
return 1;
}


Expand Down Expand Up @@ -93,14 +94,14 @@ sub loadFromCommandLine {
}

$stats{valid}++;
push @queue, [ $arg, $DEFAULT_IP, $DEFAULT_PORT ];
push @queue, [ $arg, $DEFAULT_TARGET, $DEFAULT_PORT ];
}
}


sub loadFromFile {
my $filename = shift;
my ($hwaddr, $ipaddr, $port);
my ($hwaddr, $target, $port);

open (my $FILE, '<', $filename) or die "open : $!";
while(<$FILE>) {
Expand All @@ -110,17 +111,17 @@ sub loadFromFile {
$stats{total}++;

chomp;
($hwaddr, $ipaddr, $port) = split;
($hwaddr, $target, $port) = split;

if (! isValidHardwareAddress($hwaddr) ) {
warn "Invalid hardware address: $hwaddr\n";
$stats{invalid}++;
next;
}

$ipaddr = $DEFAULT_IP unless defined($ipaddr);
if (! isValidIPAddress($ipaddr) ) {
warn "Invalid IP address: $ipaddr\n";
$target = $DEFAULT_TARGET unless defined($target);
if (! isValidTarget($target) ) {
warn "Invalid target: $target\n";
$stats{invalid}++;
next;
}
Expand All @@ -133,7 +134,7 @@ sub loadFromFile {
}

$stats{valid}++;
push @queue, [ $hwaddr, $ipaddr, $port ];
push @queue, [ $hwaddr, $target, $port ];
}
close $FILE;
}
Expand All @@ -151,7 +152,7 @@ sub loadFromFile {
#

sub wake {
my ($sock, $hwaddr, $ipaddr, $port) = @_;
my ($hwaddr, $target, $port) = @_;
my ($raddr, $them, $pkt);
#
# Expects hardware address in canonical form (xx:xx:xx:xx:xx:xx)
Expand All @@ -169,18 +170,50 @@ sub wake {
# Send packet
#

# Patch by Eugenio Jarosiewicz <ej0 at mac.com>
# $raddr = inet_aton($ipaddr);
#
$raddr = gethostbyname($ipaddr);
$them = pack_sockaddr_in($port, $raddr);
my ($err_gai, @res) = getaddrinfo($target, $port, { family => AF_UNSPEC, protocol => IPPROTO_UDP } );

if ($err_gai) {
warn "getaddrinfo failed on $target with port $port: $err_gai";
return 0;
}

if (! @res) {
warn "failed to resolve $target with port $port";
return 0;
}

my ($addr, $sock);

foreach my $ai (@res) {
my $candidate = IO::Socket->new();

$candidate->socket($ai->{family}, $ai->{socktype}, $ai->{protocol}) or next;

$sock = $candidate;
$addr = $ai->{addr};
last;
}

if (! $sock) {
warn "failed to create socket for $target with port $port";
return 0;
}

print "Sending magic packet to $ipaddr:$port with payload $hwaddr\n"
setsockopt($sock, SOL_SOCKET, SO_BROADCAST, 1) or die "setsockopt : $!";

my ($gni_err, $ipaddr) = getnameinfo($addr, NI_NUMERICHOST, NIx_NOSERV);

if ($gni_err) {
$ipaddr = $target;
}

print "Sending magic packet to $ipaddr on port $port with payload $hwaddr\n"
if $verbose;

#send($sock, $pkt, 0, $them) or die "send : $!";
send($sock, $pkt, 0, $them) unless $dryrun;
$sock->send($pkt, 0, $addr) unless $dryrun;
$sock->shutdown(SHUT_RDWR);

return 1;
}


Expand All @@ -191,20 +224,14 @@ sub sendMagicPackets {
return;
}

my $sock;
my $proto = getprotobyname('udp');

socket($sock, AF_INET, SOCK_DGRAM, $proto) or die "socket : $!";
setsockopt($sock, SOL_SOCKET, SO_BROADCAST, 1) or die "setsockopt : $!";

for my $ref (@queue) {
if (wake($ref->[0], $ref->[1], $ref->[2])) {
$stats{sent}++;
next;
}

wake($sock, $ref->[0], $ref->[1], $ref->[2]);

$stats{sent}++;
$stats{failed}++;
}

close $sock;
}

######################################################################
Expand All @@ -219,7 +246,7 @@ GetOptions(
"h|help" => sub { pod2usage( -exitval => 0, -verbose => 1); },
"v|version" => sub { print "wakeonlan $VERSION\n"; exit(0); },
"q|quiet" => sub { $verbose = 0; },
"i|ip=s" => \$DEFAULT_IP,
"i|ip=s" => \$DEFAULT_TARGET,
"p|port=i" => \$DEFAULT_PORT,
"f|file=s" => \$filename,
"n|dry-run" => sub { $dryrun = 1; },
Expand All @@ -235,8 +262,8 @@ if (! isValidPort($DEFAULT_PORT)) {
exit(2);
}

if (! isValidIPAddress($DEFAULT_IP)) {
warn "Invalid default IP address: $DEFAULT_IP\n";
if (! isValidTarget($DEFAULT_TARGET)) {
warn "Invalid default target: $DEFAULT_TARGET\n";
exit(3);
}

Expand Down Expand Up @@ -268,7 +295,7 @@ sendMagicPackets();
if ($verbose) {
printf "Hardware addresses: <total=%d, valid=%d, invalid=%d>\n",
$stats{total}, $stats{valid}, $stats{invalid};
printf "Magic packets: <sent=%d>\n", $stats{sent};
printf "Magic packets: <sent=%d, failed=%d>\n", $stats{sent}, $stats{failed};
}

exit 0;
Expand Down