From c6c44769e246d4d73f5beffe5b40f8097461b746 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 10 Jan 2020 10:38:33 +0100 Subject: [WakeOnLan] Native Java implementation Gets rid of any dependency on installed tools. --- .../org/openslx/taskmanager/tasks/WakeOnLan.java | 140 +++++++++++++++------ 1 file changed, 102 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/openslx/taskmanager/tasks/WakeOnLan.java b/src/main/java/org/openslx/taskmanager/tasks/WakeOnLan.java index 36ec7d6..74b776d 100644 --- a/src/main/java/org/openslx/taskmanager/tasks/WakeOnLan.java +++ b/src/main/java/org/openslx/taskmanager/tasks/WakeOnLan.java @@ -1,30 +1,41 @@ package org.openslx.taskmanager.tasks; -import java.util.Arrays; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -import org.openslx.satserver.util.Exec; -import org.openslx.satserver.util.Exec.ExecCallback; import org.openslx.satserver.util.Util; import org.openslx.taskmanager.api.AbstractTask; import com.google.gson.annotations.Expose; -public class WakeOnLan extends AbstractTask implements ExecCallback +public class WakeOnLan extends AbstractTask { - + + private static final Pattern RE_IPv4 = + Pattern.compile( "^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$" ); + private static final Pattern RE_MAC = + Pattern.compile( "^([0-9a-f]{2})[:-]([0-9a-f]{2}+)[:-]([0-9a-f]{2}+)[:-]([0-9a-f]{2}+)[:-]([0-9a-f]{2}+)[:-]([0-9a-f]{2}+)$", + Pattern.CASE_INSENSITIVE ); + @Expose private String[] macs; - + @Expose private String ip; - + @Expose private String password; - - private String[] cmdline; private StatusObject status; + private byte[] buffer; + @Override protected boolean initTask() { @@ -36,48 +47,101 @@ public class WakeOnLan extends AbstractTask implements ExecCallback if ( Util.isEmpty( ip ) ) { status.addMsg( "IP empty" ); } + int pwlen = 0; + Matcher m = null; + if ( !Util.isEmpty( password ) ) { + m = RE_IPv4.matcher( password ); + if ( m.matches() ) { + pwlen = 4; + } else { + m = RE_MAC.matcher( password ); + if ( m.matches() ) { + pwlen = 6; + } + } + if ( pwlen == 0 ) { + status.addMsg( "Invalid password format: " + password ); + } + } if ( !Util.isEmpty( status.messages ) ) return false; - int xlen = Util.isEmpty( password ) ? 0 : 2; - cmdline = new String[macs.length + 3 + xlen]; - cmdline[0] = "jawol"; - cmdline[1] = "-d"; - cmdline[2] = ip; - if ( xlen != 0 ) { - cmdline[3] = "-p"; - cmdline[4] = password; + buffer = new byte[ 17 * 6 + pwlen ]; + if ( pwlen != 0 ) { + try { + for ( int i = 0; i < pwlen; ++i ) { + int x = Integer.parseInt( m.group( i + 1 ), pwlen == 4 ? 10 : 16 ); + buffer[17 * 6 + i] = (byte)x; + } + } catch ( Throwable t ) { + status.addMsg( "Invalid octet in password" ); + return false; + } } - System.arraycopy( macs, 0, cmdline, 3 + xlen, macs.length ); - status.addMsg( "Command line: " + Arrays.toString( cmdline ) ); return true; } @Override protected boolean execute() { - int ret; - for ( int i = 0; i < 2; ++i ) { - ret = Exec.sync( 1, this, cmdline ); - if ( ret != 0 ) - return false; + DatagramSocket sock = null; + try { try { - Thread.sleep( 800 ); - } catch ( InterruptedException e ) { + sock = new DatagramSocket(); + sock.setBroadcast( true ); + } catch ( SocketException e ) { + status.addMsg( "Cannot create UDP socket" ); + return false; } - } - return Exec.sync( 1, cmdline ) == 0; - } - @Override - public void processStdOut( String line ) - { - status.addMsg( line ); - } + // Sync bytes + for ( int i = 0; i < 6; ++i ) { + buffer[i] = -1; + } - @Override - public void processStdErr( String line ) - { - status.addMsg( line ); + // Repeat three times + for ( int reps = 0; reps < 3; ++reps ) { + if ( reps != 0 ) { + try { + Thread.sleep( 600 ); + } catch ( InterruptedException t ) { + Thread.currentThread().interrupt(); + break; + } + } + // For each MAC + for ( int mi = 0; mi < macs.length; ++mi ) { + Matcher m = RE_MAC.matcher( macs[mi] ); + if ( !m.matches() ) { + status.addMsg( "Cannot parse MAC address " + macs[mi] ); + continue; + } + try { + for ( int i = 0; i < 6; ++i ) { + byte x = (byte)Integer.parseInt( m.group( i + 1 ), 16 ); + for ( int offset = 0; offset < 16; ++offset ) { + buffer[6 + offset * 6 + i] = x; + } + } + } catch ( NumberFormatException e ) { + status.addMsg( "Invalid octet in MAC address: " + macs[mi] ); + continue; + } + DatagramPacket dp = new DatagramPacket( buffer, buffer.length, InetAddress.getByName( ip ), 9 ); + try { + sock.send( dp ); + status.addMsg( "Sent packet to " + macs[mi] ); + } catch ( IOException e ) { + status.addMsg( "Error sending UDP packet to " + ip + "/" + macs[mi] + ": " + e.toString() ); + } + } + } + } catch ( UnknownHostException e ) { + status.addMsg( "Cannot resolve " + ip ); + return false; + } finally { + Util.multiClose( sock ); + } + return true; } class StatusObject -- cgit v1.2.3-55-g7522