[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[vidalia-svn] r2638: Merge the UPnP backend changes into trunk. (in vidalia: . trunk/src/vidalia trunk/src/vidalia/config)



Author: edmanm
Date: 2008-06-01 19:42:53 -0400 (Sun, 01 Jun 2008)
New Revision: 2638

Added:
   vidalia/trunk/src/vidalia/config/upnpcontrolthread.cpp
   vidalia/trunk/src/vidalia/config/upnpcontrolthread.h
Modified:
   vidalia/
   vidalia/trunk/src/vidalia/CMakeLists.txt
   vidalia/trunk/src/vidalia/config/serversettings.cpp
   vidalia/trunk/src/vidalia/config/serversettings.h
   vidalia/trunk/src/vidalia/config/upnpcontrol.cpp
   vidalia/trunk/src/vidalia/config/upnpcontrol.h
Log:
 r412@thebe:  edmanm | 2008-06-01 19:23:32 -0400
 Merge the UPnP backend changes into trunk.



Property changes on: vidalia
___________________________________________________________________
 svk:merge ticket from /local/vidalia [r412] on 45a62a8a-8088-484c-baad-c7b3e776dd32

Modified: vidalia/trunk/src/vidalia/CMakeLists.txt
===================================================================
--- vidalia/trunk/src/vidalia/CMakeLists.txt	2008-06-01 23:42:50 UTC (rev 2637)
+++ vidalia/trunk/src/vidalia/CMakeLists.txt	2008-06-01 23:42:53 UTC (rev 2638)
@@ -93,9 +93,11 @@
   include_directories(${MINIUPNPC_INCLUDE_DIR})
   set(vidalia_SRCS ${vidalia_SRCS}
     config/upnpcontrol.cpp
+    config/upnpcontrolthread.cpp
   )
   qt4_wrap_cpp(vidalia_SRCS
     config/upnpcontrol.h
+    config/upnpcontrolthread.h
   )
 endif(USE_MINIUPNPC)
 
@@ -257,7 +259,7 @@
   torcontrol
 )
 if (USE_MINIUPNPC)
-  target_link_libraries(${vidalia_BIN} ${MINIUPNPC_LIBRARY})
+  target_link_libraries(${vidalia_BIN} miniupnpc)
 endif(USE_MINIUPNPC)
 
 if (MINGW)

Modified: vidalia/trunk/src/vidalia/config/serversettings.cpp
===================================================================
--- vidalia/trunk/src/vidalia/config/serversettings.cpp	2008-06-01 23:42:50 UTC (rev 2637)
+++ vidalia/trunk/src/vidalia/config/serversettings.cpp	2008-06-01 23:42:53 UTC (rev 2638)
@@ -43,6 +43,7 @@
 #define SETTING_BANDWIDTH_RATE  "BandwidthRate"
 #define SETTING_BANDWIDTH_BURST "BandwidthBurst"
 #define SETTING_BRIDGE_RELAY    "BridgeRelay"
+#define SETTING_ENABLE_UPNP     "EnableUPnP"
 #define SETTING_RELAY_BANDWIDTH_RATE   "RelayBandwidthRate"
 #define SETTING_RELAY_BANDWIDTH_BURST  "RelayBandwidthBurst"
 #define SETTING_PUBLISH_DESCRIPTOR     "PublishServerDescriptor"
@@ -72,6 +73,7 @@
   setDefault(SETTING_PUBLISH_DESCRIPTOR,    "1");
   setDefault(SETTING_EXITPOLICY,
     ExitPolicy(ExitPolicy::Default).toString());
+  setDefault(SETTING_ENABLE_UPNP, false); 
 }
 
 /** Returns a QHash of Tor-recognizable configuratin keys to their current
@@ -134,10 +136,9 @@
 {
   bool rc;
 
+  configurePortForwarding();
+
   if (isServerEnabled()) {
-    /* Configure UPnP device to forward DirPort and OrPort */
-    /* TODO: does isServerEnabled() return true when a server is just set up? */
-    configurePortForwarding(true);
     rc = torControl()->setConf(confValues(), errmsg);
   } else { 
     QStringList resetKeys;
@@ -156,7 +157,6 @@
                 << SETTING_BANDWIDTH_BURST;
     }
     rc = torControl()->resetConf(resetKeys, errmsg);
-    configurePortForwarding(false);
   }
   return rc;
 }
@@ -164,21 +164,42 @@
 /* TODO: We should call this periodically, in case the router gets rebooted or forgets its UPnP settings */
 /* TODO: Remove port forwarding when Tor is shutdown or the ORPort changes */
 /* TODO: init_upnp() will block for up to 2 seconds. We should fire off a thread */
-/** Configure UPnP device to forward DirPort and ORPort */
+
+/** Configure UPnP device to forward DirPort and ORPort. If enable is
+true, will forward ORPort and DirPort; otherwise will remove exising
+port mappings */
 void
-ServerSettings::configurePortForwarding(bool enable)
+ServerSettings::configurePortForwarding()
 {
 #ifdef USE_MINIUPNPC
-  UPNPControl *pUNPControl = UPNPControl::Instance();
+  quint16 ORPort, DirPort;
+  bool enableORPort, enableDirPort;
 
-  if (enable) {
-    pUNPControl->forwardPort(getORPort());
-  } else {
-    pUNPControl->disableForwarding();
-  }
+  // This is how the tickbox should control UPNP
+  if (!isUpnpEnabled())
+    return;
+
+  ORPort = getORPort();
+  if (!isServerEnabled())
+    ORPort = 0;
+
+  DirPort = getDirPort();
+  if (!isServerEnabled() || !isDirectoryMirror())
+    DirPort = 0;
+
+  UPNPControl *control = UPNPControl::instance();
+  control->setDesiredState(DirPort, ORPort);
 #endif
 }
 
+void
+ServerSettings::cleanupPortForwarding()
+{
+#ifdef USE_MINIUPNPC
+  UPNPControl::cleanup();
+#endif
+}
+
 /** Virtual method called when we retrieve a server-related setting from Tor.
  * Currently this just translates BandwidthFoo to RelayBandwidthFoo when
  * appropriate. */
@@ -348,3 +369,24 @@
   setValue(SETTING_BANDWIDTH_BURST, rate);
 }
 
+/** Returns true if UPnP support is available and enabled. */
+bool
+ServerSettings::isUpnpEnabled()
+{
+#if defined(USE_MINIUPNPC)
+  return localValue(SETTING_ENABLE_UPNP).toBool();
+#else
+  return false;
+#endif
+}
+
+/** Sets whether Vidalia should try to configure port forwarding using UPnP.
+ * If Vidalia was compiled without UPnP support, this method has no effect. */
+void
+ServerSettings::setUpnpEnabled(bool enabled)
+{
+#if defined(USE_MINIUPNPC)
+  setValue(SETTING_ENABLE_UPNP, enabled);
+#endif
+}
+

Modified: vidalia/trunk/src/vidalia/config/serversettings.h
===================================================================
--- vidalia/trunk/src/vidalia/config/serversettings.h	2008-06-01 23:42:50 UTC (rev 2637)
+++ vidalia/trunk/src/vidalia/config/serversettings.h	2008-06-01 23:42:53 UTC (rev 2638)
@@ -82,6 +82,16 @@
   /** Gets the maximum burst rate (in B/s) of this server. */
   quint32 getBandwidthBurstRate();
 
+  /** Configure port forwarding. */
+  void configurePortForwarding();
+
+  void cleanupPortForwarding();
+
+  /** Returns true if UPnP support is enabled. */
+  bool isUpnpEnabled();
+  /** Sets whether Vidalia should try to configure port forwarding using UPnP. */
+  void setUpnpEnabled(bool enabled);
+
 protected:
   /** Virtual method called when we retrieve a server-related setting from Tor.
    * Currently this just translates BandwidthFoo to RelayBandwidthFoo when
@@ -91,9 +101,6 @@
 private:
   /** Returns Tor-recognizable configuration keys and current values. */
   QHash<QString,QString> confValues();
-
-  /** Configure UPnP device to forward DirPort and ORPort */
-  void configurePortForwarding(bool enable);
 };
 
 #endif

Modified: vidalia/trunk/src/vidalia/config/upnpcontrol.cpp
===================================================================
--- vidalia/trunk/src/vidalia/config/upnpcontrol.cpp	2008-06-01 23:42:50 UTC (rev 2637)
+++ vidalia/trunk/src/vidalia/config/upnpcontrol.cpp	2008-06-01 23:42:53 UTC (rev 2638)
@@ -16,109 +16,149 @@
 
 #include "upnpcontrol.h"
 
-UPNPControl* UPNPControl::pInstance = 0;
-UPNPControl* UPNPControl::Instance()
+#include <QMutex>
+#include <QMetaType>
+
+#ifdef Q_OS_WIN32
+#include <winsock2.h>
+#endif
+
+#include "upnpcontrolthread.h"
+
+/** UPNPControl singleton instance. */
+UPNPControl* UPNPControl::_instance = 0;
+
+/** Returns a pointer to this object's singleton instance. */
+UPNPControl* UPNPControl::instance()
 {
-  if (0 == pInstance)
-    pInstance = new UPNPControl;
-  return pInstance;
+  if (0 == _instance)
+    _instance = new UPNPControl;
+  return _instance;
 }
 
+/** Constructor. Initializes and starts a thread in which all blocking UPnP
+ * operations will be performed. */
 UPNPControl::UPNPControl()
 {
-  init_upnp();
-  forwardedPort = 0;
+  _forwardedORPort = 0;
+  _forwardedDirPort = 0;
+  _error = UnknownError;
+  _state = IdleState;
+  
+  qRegisterMetaType<UPNPControl::UPNPError>("UPNPControl::UPNPError");
+  qRegisterMetaType<UPNPControl::UPNPState>("UPNPControl::UPNPState");
+
+  _mutex = new QMutex();
+  _controlThread = new UPNPControlThread(this);
+  _controlThread->start();
 }
 
-int
-UPNPControl::forwardPort(quint16 port)
+/** Destructor. cleanup() should be called before the object is destroyed.
+ * \sa cleanup()
+ */
+UPNPControl::~UPNPControl()
 {
-  int retval;
-  
-  char sPort[6];
-  
-  char intClient[16];
-  char intPort[6];
+  delete _mutex;
+  delete _controlThread;
+}
 
-  // Convert the port number to a string
-  snprintf(sPort, sizeof(sPort), "%d", port);
+/** Terminates the UPnP control thread and frees memory allocated to this
+ * object's singleton instance. */
+void
+UPNPControl::cleanup()
+{
+  _instance->_controlThread->stop();
+  delete _instance;
+  _instance = 0;
+}  
 
-  // Add the port mapping of external:port -> internal:port
-  retval = UPNP_AddPortMapping(urls.controlURL, data.servicetype,
-			       sPort, sPort, lanaddr, "Tor server", "TCP");
-  if(UPNPCOMMAND_SUCCESS != retval) {
-    printf("AddPortMapping(%s, %s, %s) failed with code %d\n",
-	   sPort, sPort, lanaddr, retval);
-    return 1;
-  }
+/** Sets <b>desiredDirPort</b> and <b>desiredOrPort</b> to the currently
+ * forwarded DirPort and ORPort values. */
+void
+UPNPControl::getDesiredState(quint16 *desiredDirPort, quint16 *desiredOrPort)
+{
+  _mutex->lock();
+  *desiredDirPort = _forwardedDirPort;
+  *desiredOrPort = _forwardedORPort;
+  _mutex->unlock();
+}
+
+/** Sets the desired DirPort and ORPort port mappings to <b>desiredDirPort</b>
+ * and <b>desiredOrPort</b>, respectively. */
+void
+UPNPControl::setDesiredState(quint16 desiredDirPort, quint16 desiredOrPort)
+{
+  _mutex->lock();
+  _forwardedDirPort = desiredDirPort;
+  _forwardedORPort = desiredOrPort;
+  _mutex->unlock();
   
-  // Check if the port mapping was accepted
-  retval = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
-					    data.servicetype,
-					    sPort, "TCP",
-					    intClient, intPort);
-  if(UPNPCOMMAND_SUCCESS != retval) {
-    printf("GetSpecificPortMappingEntry() failed with code %d\n", retval);
-    return 2;
-  }
+  _controlThread->wakeup();
+}
+
+/** Sets the most recent UPnP-related error to <b>error</b> and emits the
+ * error() signal. */
+void
+UPNPControl::setError(UPNPError upnpError)
+{
+  _mutex->lock();
+  _error = upnpError;
+  _mutex->unlock();
   
-  if(! intClient[0]) {
-    printf("GetSpecificPortMappingEntry failed.\n");
-    return 3;
-  }
-  
-  // Output the mapping
-  printf("(external):%s -> %s:%s\n", sPort, intClient, intPort);
-  fflush(stdout);
+  emit error(upnpError);
+}
 
-  // Save the mapping
-  forwardedPort = port;
+/** Sets the current UPnP state to <b>state</b> and emits the stateChanged()
+ * signal. */
+void
+UPNPControl::setState(UPNPState state)
+{
+  _mutex->lock();
+  _state = state;
+  _mutex->unlock();
 
-  return 0;
+  emit stateChanged(state);
 }
 
-int
-UPNPControl::disableForwarding()
+/** Returns the type of error that occurred last. */
+UPNPControl::UPNPError
+UPNPControl::error() const
 {
-  char sPort[6];
+  QMutexLocker locker(_mutex);
+  return _error;
+}
 
-  if (0 == forwardedPort)
-    return 0;
+/** Returns a QString describing the type of error that occurred last. */
+QString
+UPNPControl::errorString() const
+{
+  UPNPError error = this->error();
 
-  // Convert the port number to a string
-  snprintf(sPort, sizeof(sPort), "%d", forwardedPort);
-
-  int retval = UPNP_DeletePortMapping(urls.controlURL, data.servicetype, sPort, "TCP");
-  if(UPNPCOMMAND_SUCCESS != retval) {
-    printf("DeletePortMapping() failed with code %d\n", retval);
-    return 1;
+  switch (error) {
+    case Success:
+      return tr("Success");
+    case NoUPNPDevicesFound:
+      return tr("No UPnP-enabled devices found");
+    case NoValidIGDsFound:
+      return tr("No valid UPnP-enabled Internet gateway devices found");
+    case WSAStartupFailed:
+      return tr("WSAStartup failed");
+    case AddPortMappingFailed:
+      return tr("Failed to add a port mapping");
+    case GetPortMappingFailed:
+      return tr("Failed to retrieve a port mapping");
+    case DeletePortMappingFailed:
+      return tr("Failed to remove a port mapping");
+    default:
+      return tr("Unknown error");
   }
-
-  // Output the cancelled mapping
-  printf("(external):%s -> <>\n", sPort);
-  fflush(stdout);
-
-  // Save the mapping
-  forwardedPort = 0;
-
-  return 0;
 }
 
-
-/** Based on http://miniupnp.free.fr/files/download.php?file=xchat-upnp20061022.patch */
-void
-UPNPControl::init_upnp()
+/** Returns the number of milliseconds to wait for devices to respond
+ * when attempting to discover UPnP-enabled IGDs. */
+int
+UPNPControl::discoverTimeout() const
 {
-  struct UPNPDev * devlist;
-  int retval;
+  return UPNPControlThread::UPNPCONTROL_DISCOVER_TIMEOUT;
+}
 
-  memset(&urls, 0, sizeof(struct UPNPUrls));
-  memset(&data, 0, sizeof(struct IGDdatas));
-
-  devlist = upnpDiscover(2000, NULL, NULL);
-  retval = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
-  printf("GetValidIGD returned: %d\n", retval);
-  fflush(stdout);
-
-  freeUPNPDevlist(devlist);
-}

Modified: vidalia/trunk/src/vidalia/config/upnpcontrol.h
===================================================================
--- vidalia/trunk/src/vidalia/config/upnpcontrol.h	2008-06-01 23:42:50 UTC (rev 2637)
+++ vidalia/trunk/src/vidalia/config/upnpcontrol.h	2008-06-01 23:42:53 UTC (rev 2638)
@@ -18,35 +18,97 @@
 #define _UPNPCONTROL_H
 
 #include <QObject>
+#include <QMutex>
 
-#define STATICLIB
-#include <miniupnpc/miniwget.h>
-#include <miniupnpc/miniupnpc.h>
-#include <miniupnpc/upnpcommands.h>
+/* Forward declaration to make it build */
+class UPNPControlThread;
 
+
 class UPNPControl : public QObject
 {
   Q_OBJECT
 
 public:
-  static UPNPControl* Instance();
-  int forwardPort(quint16 port);
-  int disableForwarding();
+  /** UPnP-related error values. */
+  enum UPNPError {
+    Success,
+    NoUPNPDevicesFound,
+    NoValidIGDsFound,
+    WSAStartupFailed,
+    AddPortMappingFailed,
+    GetPortMappingFailed,
+    DeletePortMappingFailed,
+    UnknownError
+  };
+  /** UPnP port forwarding state. */
+  enum UPNPState {
+    IdleState,
+    ErrorState, 
+    DiscoverState,
+    UpdatingORPortState,
+    UpdatingDirPortState,
+    ForwardingCompleteState
+  };
+
+  /** Returns a pointer to this object's singleton instance. */
+  static UPNPControl* instance();
+  /** Terminates the UPnP control thread and frees memory allocated to this
+   * object's singleton instance. */
+  static void cleanup();
+  /** Sets <b>desiredDirPort</b> and <b>desiredOrPort</b> to the currently
+   * forwarded DirPort and ORPort values. */
+  void getDesiredState(quint16 *desiredDirPort, quint16 *desiredOrPort);
+  /** Sets the desired DirPort and ORPort port mappings to
+   * <b>desiredDirPort</b> and <b>desiredOrPort</b>, respectively. */
+  void setDesiredState(quint16 desiredDirPort, quint16 desiredOrPort);
+
+  /** Returns the type of error that occurred last. */
+  UPNPError error() const;
+  /** Returns a QString describing the type of error that occurred last. */
+  QString errorString() const;
+
+  /** Returns the number of milliseconds to wait for devices to respond
+   * when attempting to discover UPnP-enabled IGDs. */
+  int discoverTimeout() const;
+
+signals:
+  /** Emitted when the UPnP control thread status changes. */
+  void stateChanged(UPNPControl::UPNPState state);
+  
+  /** Emitted when a UPnP error occurs. */
+  void error(UPNPControl::UPNPError error);
+ 
 protected:
+  /** Constructor. Initializes and starts a thread in which all blocking UPnP
+   * operations will be performed. */
   UPNPControl();
+  /** Destructor. cleanup() should be called before the object is destroyed. */
+  ~UPNPControl();
+
+  /** Sets the most recent UPnP-related error to <b>error</b> and emits the
+   * error() signal.
+   * \sa error
+   */
+  void setError(UPNPError error);
+  
+  /** Sets the current UPnP state to <b>state</b> and emits the stateChanged()
+   * signal.
+   * \sa stateChanged
+   */
+  void setState(UPNPState state);
+
 private:
-  static UPNPControl* pInstance;
+  static UPNPControl* _instance; /**< UPNPControl singleton instance. */
 
-  /** Used by miniupnpc library */
-  struct UPNPUrls urls;
-  struct IGDdatas data;
-  char lanaddr[16];
-  void init_upnp();
-  void upnp_add_redir (const char * addr, int port);
-  void upnp_rem_redir(int port);
+  quint16 _forwardedORPort; /**< Currently forwarded ORPort. */
+  quint16 _forwardedDirPort; /**< Currently forwarded DirPort. */
+  QMutex* _mutex; /**< Mutex around variables shared with UPNPControlThread. */
+  UPNPError _error; /**< Most recent UPNP error. */
+  UPNPState _state; /**< Current UPNP status. */
 
-  /* Currently forwarded port */
-  quint16 forwardedPort;
+  friend class UPNPControlThread;
+  UPNPControlThread* _controlThread; /**< Thread used for UPnP operations. */
 };
 
-#endif 
+#endif
+

Copied: vidalia/trunk/src/vidalia/config/upnpcontrolthread.cpp (from rev 2635, vidalia/branches/upnp/src/vidalia/config/upnpcontrolthread.cpp)
===================================================================
--- vidalia/trunk/src/vidalia/config/upnpcontrolthread.cpp	                        (rev 0)
+++ vidalia/trunk/src/vidalia/config/upnpcontrolthread.cpp	2008-06-01 23:42:53 UTC (rev 2638)
@@ -0,0 +1,338 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If 
+**  you did not receive the LICENSE file with this file, you may obtain it
+**  from the Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to
+**  the terms described in the LICENSE file.
+*/
+
+/* 
+** \file upnpcontrolthread.cpp
+** \version $Id$
+** \brief Thread for configuring UPnP in the background
+*/
+
+#include "upnpcontrolthread.h"
+
+#include <QWaitCondition>
+#include <QMutex>
+#include <QTime>
+#include <QTextStream>
+#include <QString>
+#include <QMessageBox>
+#include <vidalia.h>
+
+#ifdef Q_OS_WIN32
+#include <winsock2.h>
+#endif
+
+#include "upnpcontrol.h"
+
+#define UPNPCONTROL_REINIT_MSEC 300000 // 5 minutes
+#define UPNPCONTROL_MAX_WAIT_MSEC 60000 // 1 minute
+
+
+/** Constructor. <b>control</b> will be used for retrieving the desired port
+ * forwarding state. */
+UPNPControlThread::UPNPControlThread(UPNPControl *control)
+{
+  _upnpInitialized = QTime();
+  _keepRunning = true;
+  _control = control;
+
+  _dirPort = 0;
+  _orPort = 0;
+
+  _waitCondition = new QWaitCondition();
+  _waitMutex = new QMutex();
+}
+
+/** Destructor. The UPnP control thread must be stopped prior to destroying
+ * this object. 
+ * \sa stop() 
+ */
+UPNPControlThread::~UPNPControlThread()
+{
+  delete _waitCondition;
+  delete _waitMutex;
+}
+
+/** Thread entry point. The thread has a main loop that periodically wakes up
+ * and updates the configured port mappings. Upon exiting, all port mappings
+ * will be removed. */
+void
+UPNPControlThread::run()
+{
+  bool shouldExit = false;
+
+  forever {
+    /* TODO: Check for switching OR/Dir port */
+    /* TODO: Check for router losing state */
+
+    configurePorts();
+
+    /* Wait for something to happen */
+    _waitMutex->lock();
+    if (_keepRunning) {
+      /* We should continue */
+      UPNPControl::instance()->setState(UPNPControl::IdleState);
+      _waitCondition->wait(_waitMutex, UPNPCONTROL_MAX_WAIT_MSEC);
+
+      /* Maybe we were asked to exit while waiting */
+      shouldExit = !_keepRunning;
+      _waitMutex->unlock();
+      if (shouldExit)
+        break;
+    } else {
+      /* We should exit */
+      _waitMutex->unlock();
+      break;
+    }
+  }
+
+  /* Remove the existing port forwards */
+  updatePort(_dirPort, 0);
+  updatePort(_orPort, 0);
+}
+
+/** Sets up port forwarding according the previously-configured desired state.
+ * The desired state is set using UPNPControl's setDesiredState() method.
+ * \sa UPNPControl::setDesiredState 
+ */
+void
+UPNPControlThread::configurePorts() {
+  quint16 desiredDirPort, desiredOrPort;
+  bool force_init = false;
+  UPNPControl::UPNPError retval = UPNPControl::Success;
+
+  /* Get desired state */
+  _control->getDesiredState(&desiredDirPort, &desiredOrPort);
+
+  /* If it's been a while since we initialized the router, or time has gone
+     backward, then maybe the router has gone away or forgotten the forwards.
+     Reset UPnP state, and re-do the port forwarding */
+  if (_upnpInitialized.isNull() || // Is this the first time we have used UPNP?
+      _upnpInitialized>QTime::currentTime() || // Has time gone backwards?
+      _upnpInitialized.addMSecs(UPNPCONTROL_REINIT_MSEC)<QTime::currentTime() // Has it been REINIT_MSEC since initialization
+      ) {
+    _upnpInitialized = QTime();
+    force_init = true;
+  }
+
+  if (!force_init) {
+    /* Configure DirPort */
+    if (desiredDirPort != _dirPort) {
+      UPNPControl::instance()->setState(UPNPControl::UpdatingDirPortState);
+      retval = updatePort(_dirPort, desiredDirPort);
+      if (retval == UPNPControl::Success)
+        _dirPort = desiredDirPort;
+      else
+        goto err;
+    }
+
+    /* Configure ORPort */
+    if (desiredOrPort != _orPort) {
+      UPNPControl::instance()->setState(UPNPControl::UpdatingORPortState);
+      retval = updatePort(_orPort, desiredOrPort);
+      if (retval == UPNPControl::Success)
+        _orPort = desiredOrPort;
+      else
+        goto err;
+    }
+  } else {
+    /* Add the mapping even if they exist already */
+    UPNPControl::instance()->setState(UPNPControl::UpdatingDirPortState);
+    retval = updatePort(0, desiredDirPort);
+    if (retval == UPNPControl::Success)
+      _dirPort = desiredDirPort;
+    else
+      goto err;
+
+    UPNPControl::instance()->setState(UPNPControl::UpdatingORPortState);
+    retval = updatePort(0, desiredOrPort);
+    if (retval == UPNPControl::Success)
+      _orPort = desiredOrPort;
+    else goto err;
+  }
+
+  UPNPControl::instance()->setState(UPNPControl::ForwardingCompleteState);
+  return;
+
+err:
+  UPNPControl::instance()->setError(retval);
+  UPNPControl::instance()->setState(UPNPControl::ErrorState);
+}
+
+/** Terminates the UPnP control thread's run() loop.
+ * \sa run()
+ */
+void
+UPNPControlThread::stop()
+{
+  /* Lock access to _keepRunning */
+  _waitMutex->lock();
+
+  /* Ask the thread to stop */
+  _keepRunning = false;
+
+  /* Wake it up if needed */
+  _waitCondition->wakeAll();
+
+  /* Unlock shared state */
+  _waitMutex->unlock();
+
+  /* Wait for it to finish */
+  wait();
+}
+
+/** Wakes up the UPnP control thread's run() loop.
+ * \sa run() 
+ */
+void
+UPNPControlThread::wakeup()
+{
+  _waitMutex->lock();
+  _waitCondition->wakeAll();
+  _waitMutex->unlock();
+}
+
+/** Updates the port mapping for <b>oldPort</b>, changing it to 
+ * <b>newPort</b>. */
+UPNPControl::UPNPError
+UPNPControlThread::updatePort(quint16 oldPort, quint16 newPort)
+{
+  UPNPControl::UPNPError retval;
+
+#ifdef Q_OS_WIN32  
+  // Workaround from http://trolltech.com/developer/knowledgebase/579
+  WSAData wsadata;
+  if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) {
+    vWarn("WSAStartup failure while updating UPnP port forwarding");
+    return UPNPControl::WSAStartupFailed;
+  }
+#endif
+
+  if (_upnpInitialized.isNull() && (oldPort != 0 || newPort != 0)) {
+    retval = initializeUPNP();
+    if (retval == UPNPControl::Success)
+      _upnpInitialized = QTime::currentTime();
+    else
+      _upnpInitialized = QTime();
+  } else {
+    retval = UPNPControl::Success;
+  }
+
+  if (retval == UPNPControl::Success && oldPort != 0)
+    retval = disablePort(oldPort);
+
+  if (retval == UPNPControl::Success && newPort != 0)
+    retval = forwardPort(newPort);
+
+#ifdef Q_OS_WIN32
+  WSACleanup();
+#endif
+
+  return retval;
+}
+
+/** Discovers UPnP-enabled IGDs on the network. Based on 
+ * http://miniupnp.free.fr/files/download.php?file=xchat-upnp20061022.patch
+ * This method will block for UPNPCONTROL_DISCOVER_TIMEOUT milliseconds. */
+UPNPControl::UPNPError
+UPNPControlThread::initializeUPNP()
+{
+  struct UPNPDev *devlist;
+  int retval;
+
+  memset(&urls, 0, sizeof(struct UPNPUrls));
+  memset(&data, 0, sizeof(struct IGDdatas));
+
+  UPNPControl::instance()->setState(UPNPControl::DiscoverState);
+
+  devlist = upnpDiscover(UPNPCONTROL_DISCOVER_TIMEOUT, NULL, NULL);
+  if (NULL == devlist) {
+    vWarn("upnpDiscover returned: NULL");
+    return UPNPControl::NoUPNPDevicesFound;
+  }
+
+  retval = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
+
+  vInfo("GetValidIGD returned: %1").arg(retval);
+
+  freeUPNPDevlist(devlist);
+
+  if (retval != 1 && retval != 2)
+    return UPNPControl::NoValidIGDsFound;
+
+  return UPNPControl::Success;
+}
+
+/** Adds a port forwarding mapping from external:<b>port</b> to
+ * internal:<b>port</b>. Returns 0 on success, or non-zero on failure. */
+UPNPControl::UPNPError
+UPNPControlThread::forwardPort(quint16 port)
+{
+  int retval;
+
+  char sPort[6];
+
+  char intClient[16];
+  char intPort[6];
+
+  // Convert the port number to a string
+  snprintf(sPort, sizeof(sPort), "%d", port);
+
+  // Add the port mapping of external:port -> internal:port
+  retval = UPNP_AddPortMapping(urls.controlURL, data.servicetype,
+                               sPort, sPort, lanaddr, "Tor relay", "TCP");
+  if(UPNPCOMMAND_SUCCESS != retval) {
+    vWarn("AddPortMapping(%1, %2, %3) failed with code %4")
+            .arg(sPort).arg(sPort).arg(lanaddr).arg(retval);
+    return UPNPControl::AddPortMappingFailed;
+  }
+
+  // Check if the port mapping was accepted
+  retval = UPNP_GetSpecificPortMappingEntry(urls.controlURL, data.servicetype,
+                                            sPort, "TCP", intClient, intPort);
+  if(UPNPCOMMAND_SUCCESS != retval) {
+    vWarn("GetSpecificPortMappingEntry() failed with code %1").arg(retval);
+    return UPNPControl::GetPortMappingFailed;
+  }
+
+  if(! intClient[0]) {
+    vWarn("GetSpecificPortMappingEntry failed.");
+    return UPNPControl::GetPortMappingFailed;
+  }
+
+  // Output the mapping
+  vInfo("(external):%1 -> %2:%3").arg(sPort).arg(intClient).arg(intPort);
+
+  return UPNPControl::Success;
+}
+
+/** Removes the port mapping for <b>port</b>. Returns 0 on success or non-zero
+ * on failure. */
+UPNPControl::UPNPError
+UPNPControlThread::disablePort(quint16 port)
+{
+  char sPort[6];
+
+  // Convert the port number to a string
+  snprintf(sPort, sizeof(sPort), "%d", port);
+
+  // Remove the mapping
+  int retval = UPNP_DeletePortMapping(urls.controlURL, data.servicetype,
+                                      sPort, "TCP");
+  if(UPNPCOMMAND_SUCCESS != retval) {
+    vWarn("DeletePortMapping() failed with code %1").arg(retval);
+    return UPNPControl::DeletePortMappingFailed;
+  }
+
+  // Output the cancelled mapping
+  vInfo("(external):%1 -> <>").arg(sPort);
+
+  return UPNPControl::Success;
+}
+

Copied: vidalia/trunk/src/vidalia/config/upnpcontrolthread.h (from rev 2628, vidalia/branches/upnp/src/vidalia/config/upnpcontrolthread.h)
===================================================================
--- vidalia/trunk/src/vidalia/config/upnpcontrolthread.h	                        (rev 0)
+++ vidalia/trunk/src/vidalia/config/upnpcontrolthread.h	2008-06-01 23:42:53 UTC (rev 2638)
@@ -0,0 +1,90 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If 
+**  you did not receive the LICENSE file with this file, you may obtain it
+**  from the Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to
+**  the terms described in the LICENSE file.
+*/
+
+/* 
+** \file upnpcontrolthread.h
+** \version $Id$
+** \brief Thread for configuring UPnP in the background
+*/
+
+#ifndef _UPNPCONTROLTHREAD_H
+#define _UPNPCONTROLTHREAD_H
+
+#include <QThread>
+#include <QMutex>
+#include <QWaitCondition>
+#include <QTime>
+#include "upnpcontrol.h"
+
+#define STATICLIB
+#include <miniupnpc/miniwget.h>
+#include <miniupnpc/miniupnpc.h>
+#include <miniupnpc/upnpcommands.h>
+
+
+class UPNPControlThread : public QThread
+{
+  Q_OBJECT
+
+public:
+  /** Specifies the number of milliseconds to wait for devices to respond
+   * when attempting to discover UPnP-enabled IGDs. */
+  static const int UPNPCONTROL_DISCOVER_TIMEOUT = 2000;
+
+  /** Constructor. <b>control</b> will be used for retrieving the desired port
+   * forwarding state. */
+  UPNPControlThread(UPNPControl *control);
+  /** Destructor. The UPnP control thread must be stopped prior to destroying
+   * this object. */
+  ~UPNPControlThread();
+  /** Terminates the UPnP control thread's run() loop. */
+  void stop();
+  /** Wakes up the UPnP control thread's run() loop. */
+  void wakeup();
+
+protected:
+  /** Thread entry point. The thread has a main loop that periodically wakes
+   * up  and updates the configured port mappings. Upon exiting, all port
+   * mappings will be removed. */
+  void run();
+
+private:
+  /** Sets up port forwarding according the previously-configured desired
+   * state. The desired state is set using UPNPControl's setDesiredState()
+   * method. */
+  void configurePorts();
+  /** Discovers UPnP-enabled IGDs on the network.  This method will block for
+   * UPNPCONTROL_DISCOVER_TIMEOUT milliseconds. */
+  UPNPControl::UPNPError initializeUPNP();
+  /** Updates the port mapping for <b>oldPort</b>, changing it to 
+   * <b>newPort</b>. */
+  UPNPControl::UPNPError updatePort(quint16 oldPort, quint16 newPort);
+  /** Adds a port forwarding mapping from external:<b>port</b> to
+   * internal:<b>port</b>. Returns 0 on success, or non-zero on failure. */
+  UPNPControl::UPNPError forwardPort(quint16 port);
+  /** Removes the port mapping for <b>port</b>. Returns 0 on success or
+   * non-zero on failure. */
+  UPNPControl::UPNPError disablePort(quint16 port);
+  
+  QTime _upnpInitialized; /**< Time at which the UPnP state was last set. */
+  bool _keepRunning; /**< True if the control thread should keep running. */
+  UPNPControl *_control; /**< Stores desired UPnP state. */
+  QWaitCondition *_waitCondition; /**< Used to wake up the control thread. */
+  QMutex *_waitMutex; /**< Mutex around shared variables. */
+  quint16 _dirPort; /**< Desired DirPort. */
+  quint16 _orPort; /**< Desired ORPort. */
+
+  /* Used by miniupnpc library */
+  struct UPNPUrls urls;
+  struct IGDdatas data;
+  char lanaddr[16];
+};
+#endif 
+