/**
* UPnP PortMapper - A tool for managing port forwardings via UPnP
* Copyright (C) 2015 Christoph Pirkl <christoph at users.sourceforge.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.chris.portmapper.router.sbbi;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import org.chris.portmapper.PortMapperApp;
import org.chris.portmapper.Settings;
import org.chris.portmapper.model.PortMapping;
import org.chris.portmapper.model.Protocol;
import org.chris.portmapper.router.AbstractRouter;
import org.chris.portmapper.router.RouterException;
import org.chris.portmapper.util.EncodingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sbbi.upnp.devices.UPNPRootDevice;
import net.sbbi.upnp.impls.InternetGatewayDevice;
import net.sbbi.upnp.messages.UPNPResponseException;
/**
* This class represents a router device and provides methods for managing port mappings and getting information about
* the router. It useses the SBBI library's {@link InternetGatewayDevice}.
*/
public class SBBIRouter extends AbstractRouter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 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;
private final PortMapperApp app;
SBBIRouter(final PortMapperApp app, final InternetGatewayDevice router) {
super(router.getIGDRootDevice().getModelName());
this.app = app;
this.router = router;
}
@Override
public String getExternalIPAddress() throws RouterException {
logger.debug("Get external IP address...");
String ipAddress;
try {
ipAddress = router.getExternalIPAddress();
} catch (final UPNPResponseException e) {
throw new RouterException("Could not get external IP", e);
} catch (final IOException e) {
throw new RouterException("Could not get external IP", e);
}
logger.info("Got external IP address " + ipAddress + " for router.");
return ipAddress;
}
@Override
public String getInternalHostName() {
logger.debug("Get internal IP address...");
final URL presentationURL = router.getIGDRootDevice().getPresentationURL();
if (presentationURL == null) {
logger.warn("Did not get presentation url");
return null;
}
final String ipAddress = presentationURL.getHost();
logger.info("Got internal host name '{}' for router.", ipAddress);
return ipAddress;
}
@Override
public int getInternalPort() {
logger.debug("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.debug("Got valid internal port {} from presentation URL '{}'", presentationUrlPort,
presentationURL);
return presentationUrlPort;
} else {
logger.debug("Got invalid port {} from presentation url '{}'", presentationUrlPort, presentationURL);
}
} else {
logger.debug("Presentation url is null");
}
final URL urlBase = router.getIGDRootDevice().getURLBase();
final int urlBasePort = urlBase.getPort();
logger.debug("Presentation URL is null or returns invalid port: using port {} of base url '{}'", urlBasePort,
urlBase);
return urlBasePort;
}
@Override
public Collection<PortMapping> getPortMappings() throws RouterException {
return new SBBIPortMappingExtractor(router, MAX_NUM_PORTMAPPINGS).getPortMappings();
}
@Override
public void logRouterInfo() throws RouterException {
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.info("Router Info: {} \t= {}", key, value);
}
logger.info("def loc: {}", rootDevice.getDeviceDefLoc());
logger.trace("def loc data: {}", rootDevice.getDeviceDefLocData());
logger.info("icons: {}", rootDevice.getDeviceIcons());
logger.info("device type: {}", rootDevice.getDeviceType());
logger.info("direct parent: {}", rootDevice.getDirectParent());
logger.info("disc udn: {}", rootDevice.getDiscoveryUDN());
logger.info("disc usn: {}", rootDevice.getDiscoveryUSN());
logger.info("udn: {}", 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 RouterException {
final String protocolString = protocol == Protocol.TCP ? "TCP" : "UDP";
final String encodedDescription = encodeIfNecessary(description);
try {
final boolean success = router.addPortMapping(encodedDescription, null, internalPort, externalPort,
internalClient, leaseDuration, protocolString);
return success;
} catch (final IOException e) {
throw new RouterException("Could not add port mapping: " + e.getMessage(), e);
} catch (final UPNPResponseException e) {
throw new RouterException("Could not add port mapping: " + e.getMessage(), e);
}
}
private String encodeIfNecessary(final String description) {
final Settings settings = app.getSettings();
if (settings == null || settings.isUseEntityEncoding()) {
return EncodingUtilities.htmlEntityEncode(description);
}
return description;
}
@Override
public void addPortMappings(final Collection<PortMapping> mappings) throws RouterException {
for (final PortMapping portMapping : mappings) {
logger.info("Adding port mapping {}", portMapping);
addPortMapping(portMapping);
}
}
@Override
public void addPortMapping(final PortMapping mapping) throws RouterException {
logger.info("Adding port mapping ()", mapping.getCompleteDescription());
addPortMapping(mapping.getDescription(), mapping.getProtocol(), mapping.getRemoteHost(),
mapping.getExternalPort(), mapping.getInternalClient(), mapping.getInternalPort(), 0);
}
@Override
public void removeMapping(final PortMapping mapping) throws RouterException {
removePortMapping(mapping.getProtocol(), mapping.getRemoteHost(), mapping.getExternalPort());
}
@Override
public void removePortMapping(final Protocol protocol, final String remoteHost, final int externalPort)
throws RouterException {
final String protocolString = (protocol.equals(Protocol.TCP) ? "TCP" : "UDP");
try {
router.deletePortMapping(remoteHost, externalPort, protocolString);
} catch (final IOException e) {
throw new RouterException("Could not remove port mapping", e);
} catch (final UPNPResponseException e) {
throw new RouterException("Could not remove port mapping", e);
}
}
@Override
public void disconnect() {
// Nothing to do right now.
}
public long getUpTime() {
// The SBBI library does not provide a method for getting the uptime.
return 0;
}
}