/** * 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.cling; import java.util.Collection; import java.util.LinkedList; import org.chris.portmapper.model.PortMapping; import org.chris.portmapper.router.RouterException; import org.chris.portmapper.router.cling.action.ActionService; import org.chris.portmapper.router.cling.action.GetPortMappingEntryAction; import org.fourthline.cling.model.message.control.IncomingActionResponseMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sbbi.upnp.impls.InternetGatewayDevice; /** * This class fetches all {@link PortMapping} from an {@link InternetGatewayDevice}. */ class ClingPortMappingExtractor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Collection<PortMapping> mappings; private boolean moreEntries; private int currentMappingNumber; /** * The maximum number of port mappings that we will try to retrieve from the router. */ private final int maxNumPortMappings; private final ActionService actionService; ClingPortMappingExtractor(final ActionService actionService, final int maxNumPortMappings) { this.actionService = actionService; this.maxNumPortMappings = maxNumPortMappings; this.mappings = new LinkedList<>(); this.moreEntries = true; this.currentMappingNumber = 0; } public Collection<PortMapping> getPortMappings() throws RouterException { /* * This is a little trick to get all port mappings. There is a method that gets the number of available port * mappings (getNatMappingsCount()), but it seems, that this method just tries to get all port mappings and * checks, if an error is returned. * * In order to speed this up, we will do the same here, but stop, when the first exception is thrown. */ while (morePortMappingsAvailable()) { logger.debug("Getting port mapping with entry number " + currentMappingNumber + "..."); try { final PortMapping portMapping = actionService .run(new GetPortMappingEntryAction(actionService.getService(), currentMappingNumber)); mappings.add(portMapping); } catch (final ClingOperationFailedException e) { handleFailureResponse(e.getResponse()); } currentMappingNumber++; } checkMaxNumPortMappingsReached(); return mappings; } /** * Check, if the max number of entries is reached and print a warning message. */ private void checkMaxNumPortMappingsReached() { if (currentMappingNumber == maxNumPortMappings) { logger.warn( "Reached max number of port mappings to get ({}). Perhaps not all port mappings where retrieved.", maxNumPortMappings); } } private boolean morePortMappingsAvailable() { return moreEntries && currentMappingNumber < maxNumPortMappings; } private void handleFailureResponse(final IncomingActionResponseMessage incomingActionResponseMessage) { if (isNoMoreMappingsException(incomingActionResponseMessage)) { moreEntries = false; logger.debug("Got no port mapping for entry number {} (status: {}). Stop getting more entries.", currentMappingNumber, incomingActionResponseMessage.getOperation().getStatusMessage()); } else { moreEntries = false; logger.info( "Got error response when fetching port mapping for entry number {}: '{}'. Stop getting more entries.", currentMappingNumber, incomingActionResponseMessage); } } /** * This method checks, if the error code of the given exception means, that no more mappings are available. * <p> * The following error codes are recognized: * <ul> * <li>SpecifiedArrayIndexInvalid: 713</li> * <li>NoSuchEntryInArray: 714</li> * <li>Invalid Args: 402 (e.g. for DD-WRT, TP-LINK TL-R460 firmware 4.7.6 Build 100714 Rel.63134n)</li> * <li>Other errors, e.g. "The reference to entity "T" must end with the ';' delimiter" or * "Content is not allowed in prolog": 899 (e.g. ActionTec MI424-WR, Thomson TWG850-4U)</li> * </ul> * See bug reports * <ul> * <li><a href= "https://sourceforge.net/tracker/index.php?func=detail&aid=1939749&group_id=213879&atid=1027466" > * https://sourceforge.net/tracker/index.php?func=detail&aid= 1939749&group_id=213879&atid=1027466</a></li> * <li><a href="http://www.sbbi.net/forum/viewtopic.php?p=394">http://www.sbbi .net/forum/viewtopic.php?p=394</a> * </li> * <li><a href= "http://sourceforge.net/tracker/?func=detail&atid=1027466&aid=3325388&group_id=213879" >http:// * sourceforge.net/tracker/?func=detail&atid=1027466&aid=3325388& group_id=213879</a></li> * <a href= "https://sourceforge.net/tracker2/?func=detail&aid=2540478&group_id=213879&atid=1027466" >https:// * sourceforge.net/tracker2/?func=detail&aid=2540478&group_id= 213879&atid=1027466</a></li> * </ul> * * @param incomingActionResponseMessage * the exception to check * @return <code>true</code>, if the given exception means, that no more port mappings are available, else * <code>false</code>. */ private boolean isNoMoreMappingsException(final IncomingActionResponseMessage incomingActionResponseMessage) { final int errorCode = incomingActionResponseMessage.getOperation().getStatusCode(); switch (errorCode) { case 713: case 714: case 402: case 899: return true; default: return false; } } }