summaryrefslogblamecommitdiffstats
path: root/src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java
blob: 1ee2f81c8714729701eaa3decbe1714a562d37d4 (plain) (tree)







































































































































                                                                                                                                                                           
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() ] );
	}

}