/*
* The MIT License
*
* Copyright 2014 sorrge.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.nyan.dch.communication.transport.tcpip.NAT;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sbbi.upnp.devices.UPNPRootDevice;
import net.sbbi.upnp.impls.InternetGatewayDevice;
import net.sbbi.upnp.messages.UPNPResponseException;
/**
*
* @author chris
*/
public class Router
{
private static final Logger logger = Logger.getLogger(Router.class.getName());
private final String name;
public String getName()
{
return name;
}
/**
* Get the the ip of the local host.
*/
public String getLocalHostAddress() throws Exception
{
logger.fine("Get IP of localhost");
final InetAddress localHostIP = getLocalHostAddressFromSocket();
// We do not want an address like 127.0.0.1
if (localHostIP.isLoopbackAddress())
{
throw new Exception("Only found a loopback address when retrieving IP of localhost");
}
return localHostIP.getHostAddress();
}
/**
* Get the ip of the local host by connecting to the router and fetching the
* ip from the socket. This only works when we are connected to the router and
* know its internal upnp port.
*
* @return the ip of the local host.
* @throws RouterException
*/
private InetAddress getLocalHostAddressFromSocket() throws Exception
{
InetAddress localHostIP = null;
try
{
// In order to use the socket method to get the address, we have to
// be connected to the router.
final int routerInternalPort = getInternalPort();
logger.log(Level.FINE, "Got internal router port {0}", routerInternalPort);
// Check, if we got a correct port number
if (routerInternalPort > 0)
{
logger.log(Level.FINE, "Creating socket to router: {0}:{1}...", new Object[]{getInternalHostName(), routerInternalPort});
try (Socket socket = new Socket(getInternalHostName(), routerInternalPort))
{
localHostIP = socket.getLocalAddress();
}
catch (final UnknownHostException e)
{
throw new Exception("Could not create socked to "
+ getInternalHostName() + ":" + routerInternalPort,
e);
}
logger.log(Level.FINE, "Got address {0} from socket.", localHostIP);
}
else
{
logger.log(Level.FINE, "Got invalid internal router port number {0}", routerInternalPort);
}
// We are not connected to the router or got an invalid port number,
// so we have to use the traditional method.
if (localHostIP == null)
{
logger.fine("Not connected to router or got invalid port number, can not use socket to determine the address of the localhost. "
+ "If no address is found, please connect to the router.");
localHostIP = InetAddress.getLocalHost();
logger.log(Level.FINE, "Got address {0} via InetAddress.getLocalHost().", localHostIP);
}
}
catch (final IOException e)
{
throw new Exception("Could not get IP of localhost.", e);
}
return localHostIP;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append(getName()).append(" (").append(getInternalHostName())
.append(")");
return sb.toString();
}
/**
* The wrapped router device.
*/
final private InternetGatewayDevice router;
/**
* The maximum number of port mappings that we will try to retrieve from the
* router.
*/
private final static int MAX_NUM_PORTMAPPINGS = 500;
Router(final InternetGatewayDevice router)
{
this.name = router.getIGDRootDevice().getModelName();
this.router = router;
}
public String getExternalIPAddress() throws Exception
{
logger.fine("Get external IP address...");
String ipAddress;
try
{
ipAddress = router.getExternalIPAddress();
}
catch (final UPNPResponseException e)
{
throw new Exception("Could not get external IP", e);
}
catch (final IOException e)
{
throw new Exception("Could not get external IP", e);
}
logger.log(Level.INFO, "Got external IP address {0} for router.", ipAddress);
return ipAddress;
}
public String getInternalHostName()
{
logger.fine("Get internal IP address...");
final URL presentationURL = router.getIGDRootDevice()
.getPresentationURL();
if (presentationURL == null)
{
logger.warning("Did not get presentation url");
return null;
}
final String ipAddress = presentationURL.getHost();
logger.info(ipAddress + "Got internal host name '" + "' for router.");
return ipAddress;
}
public int getInternalPort()
{
logger.fine("Get internal port of router...");
final URL presentationURL = router.getIGDRootDevice()
.getPresentationURL();
// Presentation URL may be null in some situations.
if (presentationURL != null)
{
final int presentationUrlPort = presentationURL.getPort();
// https://sourceforge.net/tracker/?func=detail&aid=3198378&group_id=213879&atid=1027466
// Some routers send an invalid presentationURL, in this case use
// URLBase.
if (presentationUrlPort > 0)
{
logger.log(Level.FINE, "Got valid internal port {0} from presentation URL.", presentationUrlPort);
return presentationUrlPort;
}
else
logger.log(Level.FINE, "Got invalid port {0} from presentation url {1}", new Object[]{presentationUrlPort, presentationURL});
}
else
logger.fine("Presentation url is null");
final int urlBasePort = router.getIGDRootDevice().getURLBase().getPort();
logger.log(Level.FINE, "Presentation URL is null or returns invalid port: using url base port {0}", urlBasePort);
return urlBasePort;
}
public Collection<PortMapping> getPortMappings() throws Exception
{
return new PortMappingExtractor(router, MAX_NUM_PORTMAPPINGS)
.getPortMappings();
}
public void logRouterInfo() throws Exception
{
final Map<String, String> info = new HashMap<>();
final UPNPRootDevice rootDevice = router.getIGDRootDevice();
info.put("friendlyName", rootDevice.getFriendlyName());
info.put("manufacturer", rootDevice.getManufacturer());
info.put("modelDescription", rootDevice.getModelDescription());
info.put("modelName", rootDevice.getModelName());
info.put("serialNumber", rootDevice.getSerialNumber());
info.put("vendorFirmware", rootDevice.getVendorFirmware());
info.put("modelNumber", rootDevice.getModelNumber());
info.put("modelURL", rootDevice.getModelURL());
info.put("manufacturerURL", rootDevice.getManufacturerURL()
.toExternalForm());
info.put("presentationURL",
rootDevice.getPresentationURL() != null ? rootDevice
.getPresentationURL().toExternalForm() : null);
info.put("urlBase", rootDevice.getURLBase().toExternalForm());
final SortedSet<String> sortedKeys = new TreeSet<>(info.keySet());
for (final String key : sortedKeys)
{
final String value = info.get(key);
logger.log(Level.INFO, "Router Info: {0} \t= {1}", new Object[]{key, value});
}
logger.log(Level.INFO, "def loc {0}", rootDevice.getDeviceDefLoc());
logger.log(Level.INFO, "def loc data {0}", rootDevice.getDeviceDefLocData());
logger.log(Level.INFO, "icons {0}", rootDevice.getDeviceIcons());
logger.log(Level.INFO, "device type {0}", rootDevice.getDeviceType());
logger.log(Level.INFO, "direct parent {0}", rootDevice.getDirectParent());
logger.log(Level.INFO, "disc udn {0}", rootDevice.getDiscoveryUDN());
logger.log(Level.INFO, "disc usn {0}", rootDevice.getDiscoveryUSN());
logger.log(Level.INFO, "udn {0}", rootDevice.getUDN());
}
private boolean addPortMapping(final String description,
final Protocol protocol, final String remoteHost,
final int externalPort, final String internalClient,
final int internalPort, final int leaseDuration)
throws Exception
{
final String protocolString = protocol == Protocol.TCP ? "TCP" : "UDP";
final String encodedDescription = description;
try
{
final boolean success = router.addPortMapping(encodedDescription,
null, internalPort, externalPort, internalClient,
leaseDuration, protocolString);
return success;
}
catch (final IOException e)
{
throw new Exception("Could not add port mapping: "
+ e.getMessage(), e);
}
catch (final UPNPResponseException e)
{
throw new Exception("Could not add port mapping: "
+ e.getMessage(), e);
}
}
public void addPortMappings(final Collection<PortMapping> mappings)
throws Exception
{
for (final PortMapping portMapping : mappings)
{
logger.info("Adding port mapping " + portMapping);
addPortMapping(portMapping);
}
}
public void addPortMapping(final PortMapping mapping)
throws Exception
{
logger.info("Adding port mapping " + mapping.getCompleteDescription());
addPortMapping(mapping.getDescription(), mapping.getProtocol(),
mapping.getRemoteHost(), mapping.getExternalPort(),
mapping.getInternalClient(), mapping.getInternalPort(), 0);
}
public void removeMapping(final PortMapping mapping) throws Exception
{
removePortMapping(mapping.getProtocol(), mapping.getRemoteHost(),
mapping.getExternalPort());
}
public void removePortMapping(final Protocol protocol,
final String remoteHost, final int externalPort)
throws Exception
{
final String protocolString = (protocol.equals(Protocol.TCP) ? "TCP"
: "UDP");
try
{
router.deletePortMapping(remoteHost, externalPort, protocolString);
}
catch (final IOException e)
{
throw new Exception("Could not remove port mapping", e);
}
catch (final UPNPResponseException e)
{
throw new Exception("Could not remove port mapping", e);
}
}
}