/**
* 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
*/
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.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 timeout to set for the initial broadcast request
*/
private static final int TIMEOUT = 3000;
/**
* 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 Map<InetAddress, GatewayDevice> devices = new HashMap<>();
/*
* 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(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 gateDev = parseMSearchReplay(receivedData);
gateDev.setLocalAddress(ip);
gateDev.loadDescription();
synchronized (devices) {
devices.put(ip, gateDev);
}
} catch (SocketTimeoutException ste) {
waitingPacket = false;
}
}
} catch (Exception e) {
// e.printStackTrace();
} finally {
if (null != ssdp) {
ssdp.close();
}
}
}
}
/**
* 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);
// ST parameter: Search Targets
String[] SEARCHTYPES = { "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
"urn:schemas-upnp-org:service:WANIPConnection:1",
"urn:schemas-upnp-org:service:WANPPPConnection:1"
// ,"upnp:rootdevice"
};
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<>();
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 parseMSearchReplay(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."))
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<>();
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;
}
}