All Implemented Interfaces:
HTTPRequestListener, DeviceChangeListener, EventListener

public class UPnPextends ControlPoint implements DeviceChangeListener, EventListener
Discovers UPnP internet gateways and forwards local ports through them.
  • Field Details

    • _log

      private final Log _log
      Logger for UPnP port forwarding.
    • _context

      private final I2PAppContext _context
      The app context.
    • ROUTER_DEVICE

      private static final String ROUTER_DEVICE
      Some schemas.
      See Also:
    • WAN_DEVICE

      private static final String WAN_DEVICE
      UPnP version 1 WAN device type.
      See Also:
    • WANCON_DEVICE

      private static final String WANCON_DEVICE
      UPnP version 1 WAN connection device type.
      See Also:
    • WAN_IP_CONNECTION

      private static final String WAN_IP_CONNECTION
      UPnP version 1 IP connection service type.
      See Also:
    • WAN_PPP_CONNECTION

      private static final String WAN_PPP_CONNECTION
      UPnP version 1 PPP connection service type.
      See Also:
    • ROUTER_DEVICE_2

      private static final String ROUTER_DEVICE_2
      IGD 2 flavors, since 0.9.34.
      See Also:
    • WAN_DEVICE_2

      private static final String WAN_DEVICE_2
      UPnP version 2 WAN device type.
      See Also:
    • WANCON_DEVICE_2

      private static final String WANCON_DEVICE_2
      UPnP version 2 WAN connection device type.
      See Also:
    • WAN_IP_CONNECTION_2

      private static final String WAN_IP_CONNECTION_2
      UPnP version 2 IP connection service type.
      See Also:
    • WAN_IPV6_CONNECTION

      private static final String WAN_IPV6_CONNECTION
      UPnP IPv6 firewall control service type.
      See Also:
    • LEASE_TIME_SECONDS

      public static final int LEASE_TIME_SECONDS
      Port mapping lease requested from the gateway, in seconds.
      See Also:
    • _router

      private Device _router
      The WAN device found on the network.
    • _service

      private Service _service
      The IPv4 port mapping service in use.
    • _service6

      private Service _service6
      The IPv6 firewall service, if found.
    • _otherUDNs

      private final Map<String,Device> _otherUDNs
      Other devices found, by unique device name.
    • _eventVars

      private final Map<String,String> _eventVars
      Last event variable values, by name.
    • _serviceLacksAPM

      private volatile boolean _serviceLacksAPM
      True if the service lacks automatic port mapping.
    • _permanentLeasesOnly

      private volatile boolean _permanentLeasesOnly
      True if only permanent leases work on this device.
    • lock

      private final Object lock
      Lock guarding the port mapping state.
    • thinksWeAreDoubleNatted

      private volatile boolean thinksWeAreDoubleNatted
      Guess that an upstream NAT defeats our mapping.
    • portsToForward

      private final Set<ForwardPort> portsToForward
      List of ports we want to forward.
    • portsForwarded

      private final Set<ForwardPort> portsForwarded
      List of ports we have actually forwarded.
    • forwardCallback

      private ForwardPortCallback forwardCallback
      Callback to call when a forward fails or succeeds.
    • PROP_ADVANCED

      private static final String PROP_ADVANCED
      Config key for the console's advanced mode.
      See Also:
    • PROP_IGNORE

      private static final String PROP_IGNORE
      Config key turning UPnP off entirely.
      See Also:
    • ALLOW_SAME_HOST

      private static final boolean ALLOW_SAME_HOST
      True to talk to UPnP on the same host as us, probably for testing.
      See Also:
    • _lastAction

      private String _lastAction
      A blocking Object.toString(). That's interesting. Cache the last ArgumentList to speed it up some. Count on listSubServices(Device, StringBuilder) to call multiple combinations of arguments so we don't get old data.
    • _lastService

      private Service _lastService
      Service of the last failed action, for the log.
    • _lastArgumentList

      private ArgumentList _lastArgumentList
      Arguments of the last failed action, for the log.
    • toStringLock

      private final Object toStringLock
      Lock for building the debug strings.
    • UINT_MAX

      private static final long UINT_MAX
      Largest unsigned 32 bit value, for lease durations.
      See Also:
    • __id

      private static final AtomicInteger __id
      Counter naming the UPnP jobs.
    • BUNDLE_NAME

      private static final String BUNDLE_NAME
      Bundle holding the translated console strings.
      See Also:
  • Constructor Details

    • UPnP

      public UPnP(I2PAppContext context, int ssdpPort, int httpPort, InetAddress[] binds)
      Starts SSDP discovery and the control HTTP service on the given ports and addresses.
      Parameters:
      context - the application context supplying the log and the property manager
      ssdpPort - the local port to run SSDP discovery on
      httpPort - the local port to run the UPnP control HTTP service on
      binds - the local addresses to bind the SSDP and HTTP sockets to
  • Method Details

    • runPlugin

      public boolean runPlugin()
      Start the UPnP client: register listeners and clear the port mappings.
      Returns:
      true if the control point started successfully
    • terminate

      public void terminate()
      WARNING - Blocking up to 2 seconds.
    • getAddress

      public DetectedIP[] getAddress()
      As we only support a single active IGD, and we don't currently have any way to get any IPv6 addresses, this will return at most one IPv4 address.

      Blocking!!!

      Returns:
      array of length 1 containing an IPv4 address, or null
    • deviceAdded

      public void deviceAdded(Device dev)
      Examines a newly discovered device and maps ports when it is a gateway.
      Specified by:
      deviceAdded in interface DeviceChangeListener
      Parameters:
      dev - the device that was added
    • registerPortMappings

      private void registerPortMappings()
      Adds the port mappings the router needs.
    • discoverService

      private List<Service> discoverService(Device router)
      Traverses the structure of the router device looking for the port mapping service. The first Service will be the IPv4 Service. The second Service, if present, will be the IPv6 Service.
      Parameters:
      router - the root device of the discovered UPnP router, whose device tree is searched for a WAN connection device
      Returns:
      a list of one (IPv4) or two (IPv4 then IPv6) services, or null if no WAN connection device was found
    • tryAddMapping

      private boolean tryAddMapping(String protocol, int port, String description, ForwardPort fp)
      Tries one port mapping on the service.
      Parameters:
      protocol - tcp or udp
      port - the external port
      description - the mapping name shown on the device
      fp - the forward port request
      Returns:
      true if the mapping was added
    • unregisterPortMappings

      public void unregisterPortMappings()
      Remove all active port mappings.
    • deviceRemoved

      public void deviceRemoved(Device dev)
      Drops a departed device and un-maps every port it had held.
      Specified by:
      deviceRemoved in interface DeviceChangeListener
      Parameters:
      dev - the device that was removed
    • eventNotifyReceived

      public void eventNotifyReceived(String uuid, long seq, String varName, String value)
      EventListener callback - unused for now - supported in miniupnpd as of 1.1.
      Specified by:
      eventNotifyReceived in interface EventListener
      Parameters:
      uuid - the subscription UUID identifying the subscription
      seq - the sequence number of this event notification
      varName - the name of the state variable that changed
      value - the new value of the state variable
    • SIDtoDevice

      private Device SIDtoDevice(String sid)
      The device holding a service with the given subscription ID.
      Parameters:
      sid - non-null
      Returns:
      device or null
    • retryOtherDevices

      private void retryOtherDevices()
      Go through the other devices, try re-adding them.
    • getLocalAddresses

      public static Set<String> getLocalAddresses()
      The addresses we want to bind to.
      Returns:
      the local addresses
    • updateInterfaces

      private void updateInterfaces()
      Update the SSDPSearchResponseSocketList, SSDPNotifySocketList, and HTTPServerList every time. Otherwise, we are just listening on the interfaces that were present when started.
    • search

      public void search()
      We override search() to update the SSDPSearchResponseSocketList, SSDPNotifySocketList, and HTTPServerList every time. Otherwise, we are just listening on the interfaces that were present when started.
      Overrides:
      search in class ControlPoint
    • stringEquals

      private static boolean stringEquals(String a, String b)
      Compare two strings, either of which could be null.
      Parameters:
      a - the first string, or null
      b - the second string, or null
      Returns:
      true if both are null, or if a is non-null and equal to b
    • isNATPresent

      private boolean isNATPresent()
      Whether we are behind an UPnP-enabled NAT/router.
      Returns:
      true if a device and service were found
    • getNATAddress

      private String getNATAddress(Service service)
      Queries the external IPv4 address, blocking on the control action.
      Parameters:
      service - non-null
      Returns:
      the external IPv4 address the NAT thinks we have. Null if we can't find it.
    • getUpstreamMaxBitRate

      private int getUpstreamMaxBitRate()
      The reported upstream bit rate in bits per second. Blocking.
      Returns:
      the reported upstream bit rate in bits per second. -1 if it's not available. Blocking.
    • getDownstreamMaxBitRate

      private int getDownstreamMaxBitRate()
      The reported downstream bit rate in bits per second. Blocking.
      Returns:
      the reported downstream bit rate in bits per second. -1 if it's not available. Blocking.
    • listStateTable

      private static void listStateTable(Service serv, StringBuilder sb)
      Append the service state table HTML to the buffer; for debug output only.
      Parameters:
      serv - the service whose state variables are listed
      sb - the buffer to append the HTML list to
    • listActionsArguments

      private static void listActionsArguments(Action action, StringBuilder sb)
      Append the action's arguments as an HTML list to the buffer; for debug output only.
      Parameters:
      action - the action whose argument names are listed
      sb - the buffer to append the HTML list to
    • listActions

      private static void listActions(Service service, StringBuilder sb)
      Append all actions of the service as an HTML list to the buffer; for debug output only. Each action's arguments are listed as well.
      Parameters:
      service - the service whose action list is dumped
      sb - the buffer to append the HTML list to
    • toString

      private String toString(String action, String arg, Service serv)
      Formats a failed action for the log.
      Parameters:
      action - the action name
      arg - the argument name
      serv - the service called
      Returns:
      the formatted text
    • toLong

      private String toLong(String action, String arg, Service serv)
      Fetch a UPnP service status variable as a number.
      Parameters:
      action - the action to invoke, e.g. GetCommonLinkProperties
      arg - the output argument holding the value
      serv - the service to query
      Returns:
      the value formatted with DataHelper.formatSize2Decimal(long), or the raw text if it is not a number, prefixed with "> " when the raw value is 2^32 - 1, the largest the device counter can hold; null if the action failed, "" if the argument is missing
    • toTime

      private String toTime(String action, String arg, Service serv)
      Fetch a UPnP service status variable as a duration.
      Parameters:
      action - the action to invoke, e.g. GetStatusInfo
      arg - the output argument holding the value
      serv - the service to query
      Returns:
      the value, given in seconds, formatted as a duration, or the raw text if it is not a number; null if the action failed, "" if the argument is missing
    • toBoolean

      private String toBoolean(String action, String arg, Service serv)
      Fetch a UPnP service status variable as a boolean.
      Parameters:
      action - the action to invoke, e.g. GetFirewallStatus
      arg - the output argument holding the value
      serv - the service to query
      Returns:
      "true" if the value is exactly "1", "false" for anything else, including a failed action or a missing argument
    • listSubServices

      private void listSubServices(Device dev, StringBuilder sb)
      Lists a device's services in the debug output.
      Parameters:
      dev - the device to list
      sb - where the text is appended
    • listSubDev

      private void listSubDev(String prefix, Device dev, StringBuilder sb)
      Lists a device and its children in the debug output.
      Parameters:
      prefix - the indentation for this level
      dev - the device to list
      sb - where the text is appended
    • renderStatusHTML

      public String renderStatusHTML()
      Warning: slow.
      Returns:
      an HTML fragment describing the discovered devices and the forwarded ports
    • addMapping

      private boolean addMapping(String protocol, int port, String description, ForwardPort fp)
      This always requests that the external port == the internal port, for now. Blocking!
      Parameters:
      protocol - "TCP" or "UDP", as the device expects it
      port - the port to open, used for both the external and the internal port
      description - text the device shows next to the mapping
      fp - the port to forward; isIP6 selects the IPv4 or IPv6 service
      Returns:
      true if the NAT accepted the mapping; false if there is no NAT, no matching service, or the device rejected the action
    • addMappingV4

      private boolean addMappingV4(Service service, String protocol, int port, String description, ForwardPort fp)
      This always requests that the external port == the internal port, for now. Blocking!
      Parameters:
      service - the WAN IPv4 connection service to send the action to
      protocol - "TCP" or "UDP", as the device expects it
      port - the port to open, used for both the external and the internal port
      description - text the device shows next to the mapping
      fp - the port being forwarded; added to the forwarded set on success
      Returns:
      true if the device accepted the AddPortMapping action; false if the device has no such action or rejects the request
    • addMappingV6

      private boolean addMappingV6(Service service, int port, UPnP.IPv6ForwardPort fp)
      This always requests that the external port == the internal port, for now. Blocking!
      Parameters:
      service - WANIPv6FirewallControl
      port - the port to open a pinhole for
      fp - the IPv6 port to forward; its address and protocol are sent, and its UID is updated with the value the device returns
      Returns:
      true if the device accepted the AddPinhole action; false if it does not support pinholing or rejects the request
    • getNewUID

      private int getNewUID()
      65536 isn't a lot, so check for dups.
      Returns:
      0 - 65535
    • getIP

      private static String getIP(Device dev)
      The local IP of the device.
      Parameters:
      dev - non-null
      Returns:
      The local IP of the device (NOT the external IP) or null
    • getOurAddress

      private String getOurAddress(String deflt)
      Bug fix: If the SSDP notify or search response sockets listen on more than one interface, cybergarage can get our IP address wrong, and then we send the wrong one to the UPnP device, which will reject it if it enforces strict addressing.

      For example, if we have interfaces 192.168.1.1 and 192.168.2.1, we could get a response from 192.168.1.99 on the 192.168.2.1 interface, but when we send something to 192.168.1.99 it will go out the 192.168.1.1 interface with a request to forward to 192.168.2.1.

      So return the address of ours that is closest to his.

      Parameters:
      deflt - the address to fall back to, normally the interface address cybergarage picked; it is also considered as a candidate
      Returns:
      our address or deflt on failure
    • removeMapping

      private boolean removeMapping(String protocol, int port, ForwardPort fp, boolean noLog)
      Removes a mapping through the matching NAT service, blocking on the action.
      Parameters:
      protocol - "TCP" or "UDP", as the device expects it
      port - the external port to close
      fp - the port being un-forwarded; isIP6 selects the IPv4 or IPv6 service
      noLog - true to suppress the log message about the result
      Returns:
      true if the mapping was removed; false if there is no NAT or no matching service for the address family
    • removeMappingV4

      private boolean removeMappingV4(Service service, String protocol, int port, ForwardPort fp, boolean noLog)
      Removes an IPv4 port mapping.
      Parameters:
      service - the WAN IPv4 connection service to send the action to
      protocol - "TCP" or "UDP", as the device expects it
      port - the external port to close
      fp - the port being un-forwarded; dropped from the forwarded set either way
      noLog - true to suppress the log message about the result
      Returns:
      true if the device accepted the DeletePortMapping action; false if it has no such action or rejects the request
    • removeMappingV6

      private boolean removeMappingV6(Service service, String protocol, int port, UPnP.IPv6ForwardPort fp, boolean noLog)
      Removes an IPv6 port mapping.
      Parameters:
      service - the WANIPv6FirewallControl service to send the action to
      protocol - unused, the pinhole is identified by its unique ID
      port - unused, the pinhole is identified by its unique ID
      fp - the IPv6 port being un-forwarded; a negative UID means nothing to remove
      noLog - true to suppress the log message about the result
      Returns:
      true if the device accepted the DeletePinhole action; false if it does not support pinholing or rejects the request
    • onChangePublicPorts

      public void onChangePublicPorts(Set<ForwardPort> ports, ForwardPortCallback cb)
      Registers a callback when the given ports change. non-blocking.
      Parameters:
      ports - non-null
      cb - the callback replaced; replacing a different one is logged as an error, and may be null to unregister
    • protoToString

      private static String protoToString(int p)
      Names a firewall protocol number.
      Parameters:
      p - the protocol number
      Returns:
      tcp, udp, or the number itself
    • registerPorts

      private void registerPorts(Set<ForwardPort> portsToForwardNow)
      Runs port registration in a background thread; postControlAction() can take many seconds, especially if it's failing, and onChangePublicPorts(Set, ForwardPortCallback) may be called from threads we don't want to slow down.
      Parameters:
      portsToForwardNow - the ports to ask the device to forward, or null to renew subscriptions only, then exit
    • unregisterPorts

      private void unregisterPorts(Set<ForwardPort> portsToForwardNow)
      Runs port unregistration in a background thread; postControlAction() can take many seconds, especially if it's failing, and onChangePublicPorts() may be called from threads we don't want to slow down, so throw this in a thread.
      Parameters:
      portsToForwardNow - the ports to ask the device to stop forwarding, non-null
    • main

      public static void main(String[] args) throws Exception
      Dumps out device info in semi-HTML format.
      Parameters:
      args - ignored; this diagnostic entry point takes no options
      Throws:
      Exception - if the control point cannot be started or the search fails
    • _t

      private final String _t(String s)
      Looks up a localised string in the console bundle.
      Parameters:
      s - the message key to look up
      Returns:
      the translated string, or s itself when no translation exists
    • _t

      private final String _t(String s, Object o)
      Looks up a localised string and substitutes one argument.
      Parameters:
      s - the message key, containing a {0} placeholder
      o - the value substituted for {0}, not translated
      Returns:
      the translated string with {0} replaced, or s itself when no translation exists
    • _t

      private final String _t(String s, Object o, Object o2)
      Looks up a localised string and substitutes two arguments.
      Parameters:
      s - the message key, containing {0} and {1} placeholders
      o - the value substituted for {0}, not translated
      o2 - the value substituted for {1}, not translated
      Returns:
      the translated string with the placeholders replaced, or s itself when no translation exists