summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java
diff options
context:
space:
mode:
authorSimon Rettberg2022-01-24 20:11:30 +0100
committerSimon Rettberg2022-01-24 20:11:30 +0100
commitc0f8e66247911138724607459143d8875bff5d69 (patch)
tree98fdf26fb166d74cc779847dce37dd76131461c9 /src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java
parentUpdate log4j because of the CVE-2021-45105 security flaw (diff)
downloadtmlite-bwlp-c0f8e66247911138724607459143d8875bff5d69.tar.gz
tmlite-bwlp-c0f8e66247911138724607459143d8875bff5d69.tar.xz
tmlite-bwlp-c0f8e66247911138724607459143d8875bff5d69.zip
[WakeOnLan] Make it multi-staged, so failed clients can be retried
Clients can now be specified with one or more ways to be woken up. The methods are tried in order, until one is found that succeeds. In case of waking a client via another client using SSH, this means that connection to the intermediate client was successful, and the provided command could be run on that client and returned 0. For directed broadcasts from the server, we consider a successful .send() on the UDP socket success, as we have no way of knowing whether it reached the destination subnet/client, but assuming the reachability data in slx-admin is accurate, this can be assumed to be true.
Diffstat (limited to 'src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java')
-rw-r--r--src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java136
1 files changed, 136 insertions, 0 deletions
diff --git a/src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java b/src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java
new file mode 100644
index 0000000..1ee2f81
--- /dev/null
+++ b/src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java
@@ -0,0 +1,136 @@
+package org.openslx.satserver.util;
+
+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.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class WakeOnLanExecutor
+{
+
+ 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 );
+
+ private byte[] buffer;
+
+ public WakeOnLanExecutor( String password ) throws RuntimeException
+ {
+ Matcher m = null;
+ int pwlen = 0;
+ 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 ) {
+ throw new RuntimeException( "Invalid password format: " + password );
+ }
+ }
+ buffer = new byte[ 17 * 6 + pwlen ];
+ // 6 Sync bytes
+ for ( int i = 0; i < 6; ++i ) {
+ buffer[i] = -1;
+ }
+ if ( pwlen != 0 ) {
+ // Append pw
+ 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 ) {
+ throw new RuntimeException( "Invalid octet in password" );
+ }
+ }
+ }
+
+ /**
+ * Send out a bunch of WOL packets to the given list of MAC addresses. Every packet will be
+ * directed to given ip, i.e. all destination MACs have to be in the same subnet.
+ *
+ * @param log For adding log outout
+ * @param macs list of MAC addresses
+ * @param ip destination IP (broadcast or directed broadcast address)
+ * @param port destination port, usually 9
+ * @return list of MACs the packet has successfully been sent to
+ */
+ public String[] execute( MessageSink log, Collection<String> macs, String ip, int port )
+ {
+ if ( port == 0 ) {
+ port = 9;
+ } else if ( port > 65535 || port < 0 ) {
+ log.addMsg( "Invalid port: " + port + " for " + ip );
+ return new String[ 0 ];
+ }
+
+ DatagramSocket sock = null;
+ InetAddress destAddr = null;
+ Set<String> success = new HashSet<>();
+ try {
+ try {
+ destAddr = InetAddress.getByName( ip );
+ sock = new DatagramSocket();
+ sock.setBroadcast( true );
+ } catch ( SocketException e ) {
+ log.addMsg( "Cannot create UDP socket" );
+ return new String[ 0 ];
+ } catch ( UnknownHostException e ) {
+ log.addMsg( "Cannot resolve " + ip );
+ return new String[ 0 ];
+ }
+
+ // Repeat three times
+ for ( int reps = 0; reps < 3; ++reps ) {
+ if ( reps != 0 ) {
+ Thread.sleep( 400 );
+ }
+ // For each MAC, send WOL packet
+ for ( String mac : macs ) {
+ Matcher m = RE_MAC.matcher( mac );
+ if ( !m.matches() ) {
+ log.addMsg( "Cannot parse MAC address " + mac );
+ continue;
+ }
+ try {
+ // Patch in the 16 repetitions of the target mac address
+ 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;
+ }
+ }
+ DatagramPacket dp = new DatagramPacket( buffer, buffer.length, destAddr, port );
+ sock.send( dp );
+ log.addMsg( "Sent packet to " + mac );
+ success.add( mac );
+ Thread.sleep( 10 );
+ } catch ( NumberFormatException e ) {
+ log.addMsg( "Invalid octet in MAC address, skipping: " + mac );
+ } catch ( IOException e ) {
+ log.addMsg( "Error sending UDP packet to " + ip + "/" + mac + ": " + e.toString() );
+ }
+ }
+ }
+ } catch ( InterruptedException t ) {
+ Thread.currentThread().interrupt();
+ } finally {
+ Util.multiClose( sock );
+ }
+
+ return success.toArray( new String[ success.size() ] );
+ }
+
+}