summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openslx/satserver/util/WakeOnLanExecutor.java
blob: 1ee2f81c8714729701eaa3decbe1714a562d37d4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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() ] );
	}

}