/
GatewayDiscover.java
392 lines (326 loc) · 12.8 KB
/
GatewayDiscover.java
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*
* weupnp - Trivial upnp java library
*
* Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Alessandro Bahgat Shehata - ale dot bahgat at gmail dot com
* Daniele Castagna - daniele dot castagna at gmail dot com
*
*/
package org.bitlet.weupnp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* Handles the discovery of GatewayDevices, via the {@link org.bitlet.weupnp.GatewayDiscover#discover()} method.
*/
public class GatewayDiscover {
/**
* The SSDP port
*/
public static final int PORT = 1900;
/**
* The broadcast address to use when trying to contact UPnP devices
*/
public static final String IP = "239.255.255.250";
/**
* The default timeout for the initial broadcast request
*/
private static final int DEFAULT_TIMEOUT = 3000;
/**
* The timeout for the initial broadcast request
*/
private int timeout = DEFAULT_TIMEOUT;
/**
* The gateway types the discover have to search.
*/
private String[] searchTypes;
/**
* The default gateway types to use in search
*/
private static final String[] DEFAULT_SEARCH_TYPES =
{
"urn:schemas-upnp-org:device:InternetGatewayDevice:1",
"urn:schemas-upnp-org:service:WANIPConnection:1",
"urn:schemas-upnp-org:service:WANPPPConnection:1"
};
/**
* A map of the GatewayDevices discovered so far.
* The assumption is that a machine is connected to up to a Gateway Device
* per InetAddress
*/
private final Map<InetAddress, GatewayDevice> devices = new HashMap<InetAddress, GatewayDevice>();
/*
* Thread class for sending a search datagram and process the response.
*/
private class SendDiscoveryThread extends Thread {
InetAddress ip;
String searchMessage;
SendDiscoveryThread(InetAddress localIP, String searchMessage) {
this.ip = localIP;
this.searchMessage = searchMessage;
}
@Override
public void run() {
DatagramSocket ssdp = null;
try {
// Create socket bound to specified local address
ssdp = new DatagramSocket(new InetSocketAddress(ip, 0));
byte[] searchMessageBytes = searchMessage.getBytes();
DatagramPacket ssdpDiscoverPacket = new DatagramPacket(searchMessageBytes, searchMessageBytes.length);
ssdpDiscoverPacket.setAddress(InetAddress.getByName(IP));
ssdpDiscoverPacket.setPort(PORT);
ssdp.send(ssdpDiscoverPacket);
ssdp.setSoTimeout(GatewayDiscover.this.timeout);
boolean waitingPacket = true;
while (waitingPacket) {
DatagramPacket receivePacket = new DatagramPacket(new byte[1536], 1536);
try {
ssdp.receive(receivePacket);
byte[] receivedData = new byte[receivePacket.getLength()];
System.arraycopy(receivePacket.getData(), 0, receivedData, 0, receivePacket.getLength());
// Create GatewayDevice from response
GatewayDevice gatewayDevice = parseMSearchReply(receivedData);
gatewayDevice.setLocalAddress(ip);
gatewayDevice.loadDescription();
// verify that the search type is among the requested ones
if (Arrays.asList(searchTypes).contains(gatewayDevice.getSt())) {
synchronized (devices) {
devices.put(ip, gatewayDevice);
break; // device added for this ip, nothing further to do
}
}
} catch (SocketTimeoutException ste) {
waitingPacket = false;
}
}
} catch (Exception e) {
// e.printStackTrace();
} finally {
if (null != ssdp) {
ssdp.close();
}
}
}
}
/**
* Constructor.
*
* By default it's looking for 3 types of gateways.
*
*/
public GatewayDiscover() {
this(DEFAULT_SEARCH_TYPES);
}
/**
* Constructor of the gateway discover service.
*
* @param st The search type you are looking for
*/
public GatewayDiscover(String st) {
this(new String[]{st});
}
/**
* Constructor.
*
* @param types The search types the discover have to look for
*/
public GatewayDiscover(String[] types) {
this.searchTypes = types;
}
/**
* Gets the timeout for socket connections of the initial broadcast request.
* @return timeout in milliseconds
*/
public int getTimeout() {
return this.timeout;
}
/**
* Sets the timeout for socket connections of the initial broadcast request.
* @param milliseconds the new timeout in milliseconds
*/
public void setTimeout(int milliseconds) {
this.timeout = milliseconds;
}
/**
* Discovers Gateway Devices on the network(s) the executing machine is
* connected to.
* <p/>
* The host may be connected to different networks via different network
* interfaces.
* Assumes that each network interface has a different InetAddress and
* returns a map associating every GatewayDevice (responding to a broadcast
* discovery message) with the InetAddress it is connected to.
*
* @return a map containing a GatewayDevice per InetAddress
* @throws SocketException
* @throws UnknownHostException
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public Map<InetAddress, GatewayDevice> discover() throws SocketException, UnknownHostException, IOException, SAXException, ParserConfigurationException {
Collection<InetAddress> ips = getLocalInetAddresses(true, false, false);
for (int i = 0; i < searchTypes.length; i++) {
String searchMessage = "M-SEARCH * HTTP/1.1\r\n" +
"HOST: " + IP + ":" + PORT + "\r\n" +
"ST: " + searchTypes[i] + "\r\n" +
"MAN: \"ssdp:discover\"\r\n" +
"MX: 2\r\n" + // seconds to delay response
"\r\n";
// perform search requests for multiple network adapters concurrently
Collection<SendDiscoveryThread> threads = new ArrayList<SendDiscoveryThread>();
for (InetAddress ip : ips) {
SendDiscoveryThread thread = new SendDiscoveryThread(ip, searchMessage);
threads.add(thread);
thread.start();
}
// wait for all search threads to finish
for (SendDiscoveryThread thread : threads)
try {
thread.join();
} catch (InterruptedException e) {
// continue with next thread
}
// If a search type found devices, don't try with different search type
if (!devices.isEmpty())
break;
} // loop SEARCHTYPES
return devices;
}
/**
* Parses the reply from UPnP devices
*
* @param reply the raw bytes received as a reply
* @return the representation of a GatewayDevice
*/
private GatewayDevice parseMSearchReply(byte[] reply) {
GatewayDevice device = new GatewayDevice();
String replyString = new String(reply);
StringTokenizer st = new StringTokenizer(replyString, "\n");
while (st.hasMoreTokens()) {
String line = st.nextToken().trim();
if (line.isEmpty())
continue;
if (line.startsWith("HTTP/1.") || line.startsWith("NOTIFY *"))
continue;
String key = line.substring(0, line.indexOf(':'));
String value = line.length() > key.length() + 1 ? line.substring(key.length() + 1) : null;
key = key.trim();
if (value != null) {
value = value.trim();
}
if (key.compareToIgnoreCase("location") == 0) {
device.setLocation(value);
} else if (key.compareToIgnoreCase("st") == 0) { // Search Target
device.setSt(value);
}
}
return device;
}
/**
* Gets the first connected gateway
*
* @return the first GatewayDevice which is connected to the network, or
* null if none present
*/
public GatewayDevice getValidGateway() {
for (GatewayDevice device : devices.values()) {
try {
if (device.isConnected()) {
return device;
}
} catch (Exception e) {
}
}
return null;
}
/**
* Returns list of all discovered gateways. Is empty when no gateway is found.
*/
public Map<InetAddress, GatewayDevice> getAllGateways() {
return devices;
}
/**
* Retrieves all local IP addresses from all present network devices.
*
* @param getIPv4 boolean flag if IPv4 addresses shall be retrieved
* @param getIPv6 boolean flag if IPv6 addresses shall be retrieved
* @param sortIPv4BeforeIPv6 if true, IPv4 addresses will be sorted before IPv6 addresses
* @return Collection if {@link InetAddress}es
*/
private List<InetAddress> getLocalInetAddresses(boolean getIPv4, boolean getIPv6, boolean sortIPv4BeforeIPv6) {
List<InetAddress> arrayIPAddress = new ArrayList<InetAddress>();
int lastIPv4Index = 0;
// Get all network interfaces
Enumeration<NetworkInterface> networkInterfaces;
try {
networkInterfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
return arrayIPAddress;
}
if (networkInterfaces == null)
return arrayIPAddress;
// For every suitable network interface, get all IP addresses
while (networkInterfaces.hasMoreElements()) {
NetworkInterface card = networkInterfaces.nextElement();
try {
// skip devices, not suitable to search gateways for
if (card.isLoopback() || card.isPointToPoint() ||
card.isVirtual() || !card.isUp())
continue;
} catch (SocketException e) {
continue;
}
Enumeration<InetAddress> addresses = card.getInetAddresses();
if (addresses == null)
continue;
while (addresses.hasMoreElements()) {
InetAddress inetAddress = addresses.nextElement();
int index = arrayIPAddress.size();
if (!getIPv4 || !getIPv6) {
if (getIPv4 && !Inet4Address.class.isInstance(inetAddress))
continue;
if (getIPv6 && !Inet6Address.class.isInstance(inetAddress))
continue;
} else if (sortIPv4BeforeIPv6 && Inet4Address.class.isInstance(inetAddress)) {
index = lastIPv4Index++;
}
arrayIPAddress.add(index, inetAddress);
}
}
return arrayIPAddress;
}
}