Author: nextgens Date: 2007-05-23 20:24:22 +0000 (Wed, 23 May 2007) New Revision: 13344
Modified: trunk/plugins/UPnP/UPnP.java Log: UPnP: basic support: port forwarding is set as soon as possible, the web interface displays the device capabilities... I'm not sure that ip detection works reliably. it needs testing. Modified: trunk/plugins/UPnP/UPnP.java =================================================================== --- trunk/plugins/UPnP/UPnP.java 2007-05-23 19:55:43 UTC (rev 13343) +++ trunk/plugins/UPnP/UPnP.java 2007-05-23 20:24:22 UTC (rev 13344) @@ -19,7 +19,6 @@ import plugins.UPnP.org.cybergarage.upnp.ServiceStateTable; import plugins.UPnP.org.cybergarage.upnp.StateVariable; import plugins.UPnP.org.cybergarage.upnp.device.DeviceChangeListener; -import plugins.UPnP.org.cybergarage.upnp.xml.StateVariableData; import freenet.pluginmanager.DetectedIP; import freenet.pluginmanager.FredPlugin; import freenet.pluginmanager.FredPluginHTTP; @@ -27,6 +26,8 @@ import freenet.pluginmanager.FredPluginThreadless; import freenet.pluginmanager.PluginHTTPException; import freenet.pluginmanager.PluginRespirator; +import freenet.support.HTMLNode; +import freenet.support.Logger; import freenet.support.api.HTTPRequest; /** @@ -40,9 +41,12 @@ * @see http://www.upnp.org/ * @see http://en.wikipedia.org/wiki/Universal_Plug_and_Play * - * TODO: add logging! + * TODO: Support multiple IGDs ? + * TODO: Advertise the node like the MDNS plugin does + * TODO: Implement EvenListener and react on ip-change */ public class UPnP extends ControlPoint implements FredPluginHTTP, FredPlugin, FredPluginThreadless, FredPluginIPDetector, DeviceChangeListener { + private PluginRespirator pr; /** some schemas */ private static final String ROUTER_DEVICE = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"; @@ -50,9 +54,12 @@ private static final String WANCON_DEVICE = "urn:schemas-upnp-org:device:WANConnectionDevice:1"; private static final String WAN_IP_CONNECTION = "urn:schemas-upnp-org:service:WANIPConnection:1"; - private volatile Device _router; - private volatile Service _service; + private Device _router; + private Service _service; + private boolean isPortForwarded = false; + private boolean isDisabled = false; // We disable the plugin if more than one IGD is found private final Object lock = new Object(); + private int fnpPortNumber; public UPnP() { super(); @@ -60,31 +67,75 @@ } public void runPlugin(PluginRespirator pr) { - start(); + this.pr = pr; + this.fnpPortNumber = pr.getNode().getFNPPort(); + super.start(); } public void terminate() { - stop(); + unregisterPortMapping(); + super.stop(); } - // FIXME: we use the first IGD we detect, so we have got only 1 ip to report public DetectedIP[] getAddress() { + Logger.minor(this, "UP&P.getAddress() is called \\o/"); + if(isDisabled) { + Logger.normal(this, "Plugin has been disabled previously, ignoring request."); + return null; + } else if(!isNATPresent()) { + Logger.normal(this, "No UP&P device found, detection of the external ip address using the plugin has failed"); + return null; + } + + DetectedIP result = null; + final String natAddress = getNATAddress(); try { - return new DetectedIP[] { new DetectedIP(InetAddress.getByName(getNATAddress()), DetectedIP.NOT_SUPPORTED) }; + // TODO: report a different connection type if port forwarding has succeeded? + result = new DetectedIP(InetAddress.getByName(natAddress), DetectedIP.NOT_SUPPORTED); + + Logger.normal(this, "Successful UP&P discovery :" + result); + System.out.println("Successful UP&P discovery :" + result); + + return new DetectedIP[] { result }; } catch (UnknownHostException e) { + Logger.error(this, "Caught an UnknownHostException resolving " + natAddress, e); + System.err.println("UP&P discovery has failed: unable to resolve " + result); return null; } } - public void deviceAdded(Device dev ) { + public void deviceAdded(Device dev) { synchronized (lock) { - if(isNATPresent()) - return; // We don't handle more than one IGD. - - if(!ROUTER_DEVICE.equals(dev.getDeviceType()) || !dev.isRootDevice()) + if(isDisabled) { + Logger.normal(this, "Plugin has been disabled previously, ignoring new device."); return; + } else if(!ROUTER_DEVICE.equals(dev.getDeviceType()) || !dev.isRootDevice()) + return; // Silently ignore non-IGD devices + else if(isNATPresent()) { + Logger.error(this, "We got a second IGD on the network! the plugin doesn't handle that: let's disable it."); + System.err.println("The UP&P plugin has found more than one IGD on the network, as a result it will be disabled"); + isDisabled = true; + + _router = null; + _service = null; + + stop(); + return; + } + Logger.normal(this, "UP&P IGD found : " + dev.getFriendlyName()); + System.out.println("UP&P IGD found : " + dev.getFriendlyName()); _router = dev; + discoverService(); + if(_service == null) { + Logger.error(this, "The IGD device we got isn't suiting our needs, let's disable the plugin"); + System.err.println("The IGD device we got isn't suiting our needs, let's disable the plugin"); + isDisabled = true; + _router = null; + } else + registerPortMapping(); + // We have found the device we need: stop the listener thread + stop(); } } @@ -104,7 +155,7 @@ if (!current2.getDeviceType().equals(WANCON_DEVICE)) continue; - + _service = current2.getService(WAN_IP_CONNECTION); return; } @@ -112,6 +163,27 @@ } } + public void registerPortMapping() { + if(isPortForwarded) { + Logger.error(this, "Port mapping already registered! shouldn't happen!"); + return; + } + + Logger.normal(this, "Registering a port mapping for " + fnpPortNumber + "/udp"); + isPortForwarded = addMapping("UDP", fnpPortNumber, "Freenet 0.7 FNP - " + _router.getInterfaceAddress()); + Logger.normal(this, isPortForwarded ? "Mapping is successful!" : "Mapping has failed!"); + } + + public void unregisterPortMapping() { + if(!isPortForwarded) + return; + + Logger.normal(this, "Unregistering the port mapping for FNP"); + isPortForwarded = false; + boolean result = removeMapping("udp", fnpPortNumber); + Logger.normal(this, result ? "Mapping removal is successful" : "Mapping removal has failed!"); + } + public void deviceRemoved(Device dev ){ synchronized (lock) { if(_router.equals(dev)) { @@ -173,10 +245,6 @@ } } - private String toString(StateVariableData data) { - return (data == null ? "null" : data.getValue()); - } - private String toString(String action, String Argument, Service serv) { Action getIP = serv.getAction(action); if(getIP == null || !getIP.postControlAction()) @@ -186,6 +254,7 @@ return ret.getValue(); } + // TODO: extend it! RTFM private void listSubServices(Device dev, StringBuffer sb) { ServiceList sl = dev.getServiceList(); for(int i=0; i<sl.size(); i++) { @@ -193,59 +262,29 @@ if(serv == null) continue; sb.append("<div>service ("+i+") : "+serv.getServiceType()+"<br>"); if("urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1".equals(serv.getServiceType())){ - StateVariable linkStatus = serv.getStateVariable("PhysicalLinkStatus"); - StateVariable wanAccessType = serv.getStateVariable("WANAccessType"); - StateVariable upstreamBW = serv.getStateVariable("Layer1UpstreamMaxBitRate"); - StateVariable downstreamBW = serv.getStateVariable("Layer1DownstreamMaxBitRate"); - sb.append("WANCommonInterfaceConfig"); - if(linkStatus != null) - sb.append(" status: " + toString(linkStatus.getStateVariableData())); - if(wanAccessType != null) - sb.append(" type: " + toString(wanAccessType.getStateVariableData())); - if(upstreamBW != null) - sb.append(" upstream: " + toString(upstreamBW.getStateVariableData())); - if(downstreamBW != null) - sb.append(" downstream: " + toString(downstreamBW.getStateVariableData()) + "<br>"); + sb.append(" status: " + toString("GetCommonLinkProperties", "NewPhysicalLinkStatus", serv)); + sb.append(" type: " + toString("GetCommonLinkProperties", "NewWANAccessType", serv)); + sb.append(" upstream: " + toString("GetCommonLinkProperties", "NewLayer1UpstreamMaxBitRate", serv)); + sb.append(" downstream: " + toString("GetCommonLinkProperties", "NewLayer1DownstreamMaxBitRate", serv) + "<br>"); }else if("urn:schemas-upnp-org:service:WANPPPConnection:1".equals(serv.getServiceType())){ - StateVariable linkStatus = serv.getStateVariable("ConnectionStatus"); - StateVariable uptime = serv.getStateVariable("Uptime"); - StateVariable upstreamBW = serv.getStateVariable("UpstreamMaxBitRate"); - StateVariable downstreamBW = serv.getStateVariable("DownstreamMaxBitRate"); - sb.append("WANPPPConnection"); - if(linkStatus != null) - sb.append(" status: " + toString(linkStatus.getStateVariableData())); - if(uptime != null) - sb.append(" uptime: " + toString(uptime.getStateVariableData())); - if(upstreamBW != null) - sb.append(" upstream: " + toString(upstreamBW.getStateVariableData())); - if(downstreamBW != null) - sb.append(" downstream: " + toString(downstreamBW.getStateVariableData()) + "<br>"); + sb.append(" status: " + toString("GetStatusInfo", "NewConnectionStatus", serv)); + sb.append(" type: " + toString("GetConnectionTypeInfo", "NewConnectionType", serv)); + sb.append(" upstream: " + toString("GetLinkLayerMaxBitRates", "NewUpstreamMaxBitRate", serv)); + sb.append(" downstream: " + toString("GetLinkLayerMaxBitRates", "NewDownstreamMaxBitRate", serv) + "<br>"); + sb.append(" external IP: " + toString("GetExternalIPAddress", "NewExternalIPAddress", serv) + "<br>"); }else if("urn:schemas-upnp-org:service:Layer3Forwarding:1".equals(serv.getServiceType())){ - StateVariable defaultConnectionService = serv.getStateVariable("DefaultConnectionService"); - if(defaultConnectionService != null) - sb.append("DefaultConnectionService: " + toString(defaultConnectionService.getStateVariableData())); + sb.append("Layer3Forwarding"); + sb.append("DefaultConnectionService: " + toString("GetDefaultConnectionService", "NewDefaultConnectionService", serv)); }else if(WAN_IP_CONNECTION.equals(serv.getServiceType())){ sb.append("WANIPConnection"); sb.append(" status: " + toString("GetStatusInfo", "NewConnectionStatus", serv)); sb.append(" type: " + toString("GetConnectionTypeInfo", "NewConnectionType", serv)); sb.append(" external IP: " + toString("GetExternalIPAddress", "NewExternalIPAddress", serv) + "<br>"); }else if("urn:schemas-upnp-org:service:WANEthernetLinkConfig:1".equals(serv.getServiceType())){ - StateVariable linkStatus = serv.getStateVariable("EthernetLinkStatus"); - sb.append("WANEthernetLinkConfig"); - if(linkStatus != null) - sb.append(" status: " + toString(linkStatus.getStateVariableData()) + "<br>"); - }else if("urn:schemas-upnp-org:service:LANHostConfigManagement:1".equals(serv.getServiceType())){ - StateVariable netmask = serv.getStateVariable("SubnetMask"); - StateVariable dnsServers = serv.getStateVariable("DNSServers"); - - sb.append("LANHostConfigManagement"); - if(netmask != null) - sb.append(" subnetMask: " + toString(netmask.getStateVariableData())); - if(dnsServers != null) - sb.append(" dnsServers: " + toString(dnsServers.getStateVariableData()) + "<br>"); + sb.append(" status: " + toString("GetEthernetLinkStatus", "NewEthernetLinkStatus", serv) + "<br>"); }else sb.append("~~~~~~~ "+serv.getServiceType()); listActions(serv, sb); @@ -271,28 +310,96 @@ } public String handleHTTPGet(HTTPRequest request) throws PluginHTTPException { - StringBuffer sb = new StringBuffer(); - sb.append("<html><body>"); + if(request.isParameterSet("getDeviceCapabilities")) { + final StringBuffer sb = new StringBuffer(); + sb.append("<html><head><title>UPnP report</title></head><body>"); + listSubDev("WANDevice", _router, sb); + sb.append("</body></html>"); + return sb.toString(); + } - sb.append("<h2>Our current ip address is : " + getNATAddress() + "</h2>"); + HTMLNode pageNode = pr.getPageMaker().getPageNode("UP&P plugin configuration page", false, null); + HTMLNode contentNode = pr.getPageMaker().getContentNode(pageNode); - if(_router != null) - listSubDev("WANDevice", _router, sb); - else - sb.append("No UPnP aware device has been found!"); + if(isDisabled) { + HTMLNode disabledInfobox = contentNode.addChild("div", "class", "infobox infobox-error"); + HTMLNode disabledInfoboxHeader = disabledInfobox.addChild("div", "class", "infobox-header"); + HTMLNode disabledInfoboxContent = disabledInfobox.addChild("div", "class", "infobox-content"); - sb.append("</body></html>"); - return sb.toString(); + disabledInfoboxHeader.addChild("#", "UP&P plugin report"); + disabledInfoboxContent.addChild("#", "The plugin has been disabled; Do you have more than one UP&P IGD on your LAN ?"); + return pageNode.generate(); + } else if(!isNATPresent()) { + HTMLNode notFoundInfobox = contentNode.addChild("div", "class", "infobox infobox-warning"); + HTMLNode notFoundInfoboxHeader = notFoundInfobox.addChild("div", "class", "infobox-header"); + HTMLNode notFoundInfoboxContent = notFoundInfobox.addChild("div", "class", "infobox-content"); + + notFoundInfoboxHeader.addChild("#", "UP&P plugin report"); + notFoundInfoboxContent.addChild("#", "The plugin hasn't found any UP&P aware, compatible device on your LAN."); + return pageNode.generate(); + } + + HTMLNode foundInfobox = contentNode.addChild("div", "class", "infobox infobox-normal"); + HTMLNode foundInfoboxHeader = foundInfobox.addChild("div", "class", "infobox-header"); + HTMLNode foundInfoboxContent = foundInfobox.addChild("div", "class", "infobox-content"); + + foundInfoboxHeader.addChild("#", "UP&P plugin report"); + foundInfoboxContent.addChild("p", "The following device has been found : ").addChild("a", "href", "?getDeviceCapabilities").addChild("#", _router.getFriendlyName()); + foundInfoboxContent.addChild("p", "Our current external ip address is : " + getNATAddress()); + if(isPortForwarded) + foundInfoboxContent.addChild("p", "The plugin has managed to configure the port mapping correctly!"); + + return pageNode.generate(); } - public String handleHTTPPost(HTTPRequest request) - throws PluginHTTPException { - // TODO Auto-generated method stub + public String handleHTTPPost(HTTPRequest request) throws PluginHTTPException { return null; } public String handleHTTPPut(HTTPRequest request) throws PluginHTTPException { - // TODO Auto-generated method stub return null; } + + private boolean addMapping(String protocol, int port, String description) { + if(isDisabled || !isNATPresent()) + return false; + + // Just in case... + removeMapping(protocol, port); + + Action add = _service.getAction("AddPortMapping"); + if(add == null) { + Logger.error(this, "Couldn't find AddPortMapping action!"); + return false; + } + + + add.setArgumentValue("NewRemoteHost", ""); + add.setArgumentValue("NewExternalPort", port); + add.setArgumentValue("NewInternalClient", _router.getInterfaceAddress()); + add.setArgumentValue("NewInternalPort", port); + add.setArgumentValue("NewProtocol", protocol); + add.setArgumentValue("NewPortMappingDescription", description); + add.setArgumentValue("NewEnabled","1"); + add.setArgumentValue("NewLeaseDuration", 0); + + return add.postControlAction(); + } + + private boolean removeMapping(String protocol, int port) { + if(isDisabled || !isNATPresent()) + return false; + + Action remove = _service.getAction("DeletePortMapping"); + if(remove == null) { + Logger.error(this, "Couldn't find DeletePortMapping action!"); + return false; + } + + // remove.setArgumentValue("NewRemoteHost", ""); + remove.setArgumentValue("NewExternalPort", port); + remove.setArgumentValue("NewProtocol", protocol); + + return remove.postControlAction(); + } } \ No newline at end of file
