UPNPControlThread.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If 
00004 **  you did not receive the LICENSE file with this file, you may obtain it
00005 **  from the Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to
00008 **  the terms described in the LICENSE file.
00009 */
00010 
00011 /* 
00012 ** \file UPNPControlThread.cpp
00013 ** \version $Id: UPNPControlThread.cpp 4252 2010-04-09 23:18:46Z edmanm $
00014 ** \brief Thread for configuring UPnP in the background
00015 */
00016 
00017 #include "UPNPControlThread.h"
00018 #include "UPNPControl.h"
00019 #include "Vidalia.h"
00020 
00021 #include <QWaitCondition>
00022 #include <QMutex>
00023 #include <QTime>
00024 #include <QTextStream>
00025 #include <QString>
00026 #include <QMessageBox>
00027 
00028 #define UPNPCONTROL_REINIT_MSEC 300000 // 5 minutes
00029 #define UPNPCONTROL_MAX_WAIT_MSEC 60000 // 1 minute
00030 
00031 
00032 /** Constructor. <b>control</b> will be used for retrieving the desired port
00033  * forwarding state. */
00034 UPNPControlThread::UPNPControlThread(UPNPControl *control)
00035 {
00036   _upnpInitialized = QTime();
00037   _keepRunning = true;
00038   _control = control;
00039 
00040   _dirPort = 0;
00041   _orPort = 0;
00042 
00043   _waitCondition = new QWaitCondition();
00044   _waitMutex = new QMutex();
00045 }
00046 
00047 /** Destructor. The UPnP control thread must be stopped prior to destroying
00048  * this object. 
00049  * \sa stop() 
00050  */
00051 UPNPControlThread::~UPNPControlThread()
00052 {
00053   delete _waitCondition;
00054   delete _waitMutex;
00055 }
00056 
00057 /** Thread entry point. The thread has a main loop that periodically wakes up
00058  * and updates the configured port mappings. Upon exiting, all port mappings
00059  * will be removed. */
00060 void
00061 UPNPControlThread::run()
00062 {
00063   bool shouldExit = false;
00064 
00065   forever {
00066     /* TODO: Check for switching OR/Dir port */
00067     /* TODO: Check for router losing state */
00068 
00069     configurePorts();
00070 
00071     /* Wait for something to happen */
00072     _waitMutex->lock();
00073     if (_keepRunning) {
00074       /* We should continue */
00075       UPNPControl::instance()->setState(UPNPControl::IdleState);
00076       _waitCondition->wait(_waitMutex, UPNPCONTROL_MAX_WAIT_MSEC);
00077 
00078       /* Maybe we were asked to exit while waiting */
00079       shouldExit = !_keepRunning;
00080       _waitMutex->unlock();
00081       if (shouldExit)
00082         break;
00083     } else {
00084       /* We should exit */
00085       _waitMutex->unlock();
00086       break;
00087     }
00088   }
00089 
00090   /* Remove the existing port forwards */
00091   updatePort(_dirPort, 0);
00092   updatePort(_orPort, 0);
00093 }
00094 
00095 /** Sets up port forwarding according the previously-configured desired state.
00096  * The desired state is set using UPNPControl's setDesiredState() method.
00097  * \sa UPNPControl::setDesiredState 
00098  */
00099 void
00100 UPNPControlThread::configurePorts()
00101 {
00102   quint16 desiredDirPort, desiredOrPort;
00103   bool force_init = false;
00104   UPNPControl::UPNPError retval = UPNPControl::Success;
00105 
00106   /* Get desired state */
00107   _control->getDesiredState(&desiredDirPort, &desiredOrPort);
00108 
00109   /* If it's been a while since we initialized the router, or time has gone
00110      backward, then maybe the router has gone away or forgotten the forwards.
00111      Reset UPnP state, and re-do the port forwarding */
00112   if (_upnpInitialized.isNull() || // Is this the first time we have used UPNP?
00113       _upnpInitialized>QTime::currentTime() || // Has time gone backwards?
00114       _upnpInitialized.addMSecs(UPNPCONTROL_REINIT_MSEC)<QTime::currentTime() // Has it been REINIT_MSEC since initialization
00115       ) {
00116     _upnpInitialized = QTime();
00117     force_init = true;
00118   }
00119 
00120   if (!force_init) {
00121     /* Configure DirPort */
00122     if (desiredDirPort != _dirPort) {
00123       UPNPControl::instance()->setState(UPNPControl::UpdatingDirPortState);
00124       retval = updatePort(_dirPort, desiredDirPort);
00125       if (retval == UPNPControl::Success)
00126         _dirPort = desiredDirPort;
00127       else
00128         goto err;
00129     }
00130 
00131     /* Configure ORPort */
00132     if (desiredOrPort != _orPort) {
00133       UPNPControl::instance()->setState(UPNPControl::UpdatingORPortState);
00134       retval = updatePort(_orPort, desiredOrPort);
00135       if (retval == UPNPControl::Success)
00136         _orPort = desiredOrPort;
00137       else
00138         goto err;
00139     }
00140   } else {
00141     /* Add the mapping even if they exist already */
00142     UPNPControl::instance()->setState(UPNPControl::UpdatingDirPortState);
00143     retval = updatePort(0, desiredDirPort);
00144     if (retval == UPNPControl::Success)
00145       _dirPort = desiredDirPort;
00146     else
00147       goto err;
00148 
00149     UPNPControl::instance()->setState(UPNPControl::UpdatingORPortState);
00150     retval = updatePort(0, desiredOrPort);
00151     if (retval == UPNPControl::Success)
00152       _orPort = desiredOrPort;
00153     else goto err;
00154   }
00155 
00156   UPNPControl::instance()->setState(UPNPControl::ForwardingCompleteState);
00157   return;
00158 
00159 err:
00160   UPNPControl::instance()->setError(retval);
00161   UPNPControl::instance()->setState(UPNPControl::ErrorState);
00162 }
00163 
00164 /** Terminates the UPnP control thread's run() loop.
00165  * \sa run()
00166  */
00167 void
00168 UPNPControlThread::stop()
00169 {
00170   /* Lock access to _keepRunning */
00171   _waitMutex->lock();
00172 
00173   /* Ask the thread to stop */
00174   _keepRunning = false;
00175 
00176   /* Wake it up if needed */
00177   _waitCondition->wakeAll();
00178 
00179   /* Unlock shared state */
00180   _waitMutex->unlock();
00181 
00182   /* Wait for it to finish */
00183   wait();
00184 }
00185 
00186 /** Wakes up the UPnP control thread's run() loop.
00187  * \sa run() 
00188  */
00189 void
00190 UPNPControlThread::wakeup()
00191 {
00192   _waitMutex->lock();
00193   _waitCondition->wakeAll();
00194   _waitMutex->unlock();
00195 }
00196 
00197 /** Updates the port mapping for <b>oldPort</b>, changing it to 
00198  * <b>newPort</b>. */
00199 UPNPControl::UPNPError
00200 UPNPControlThread::updatePort(quint16 oldPort, quint16 newPort)
00201 {
00202   UPNPControl::UPNPError retval;
00203 
00204 #ifdef Q_OS_WIN32  
00205   // Workaround from http://trolltech.com/developer/knowledgebase/579
00206   WSAData wsadata;
00207   if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) {
00208     vWarn("WSAStartup failure while updating UPnP port forwarding");
00209     return UPNPControl::WSAStartupFailed;
00210   }
00211 #endif
00212 
00213   if (_upnpInitialized.isNull() && (oldPort != 0 || newPort != 0)) {
00214     retval = initializeUPNP();
00215     if (retval == UPNPControl::Success)
00216       _upnpInitialized = QTime::currentTime();
00217     else
00218       _upnpInitialized = QTime();
00219   } else {
00220     retval = UPNPControl::Success;
00221   }
00222 
00223   if (retval == UPNPControl::Success && oldPort != 0)
00224     retval = disablePort(oldPort);
00225 
00226   if (retval == UPNPControl::Success && newPort != 0)
00227     retval = forwardPort(newPort);
00228 
00229 #ifdef Q_OS_WIN32
00230   WSACleanup();
00231 #endif
00232 
00233   return retval;
00234 }
00235 
00236 /** Discovers UPnP-enabled IGDs on the network. Based on 
00237  * http://miniupnp.free.fr/files/download.php?file=xchat-upnp20061022.patch
00238  * This method will block for UPNPCONTROL_DISCOVER_TIMEOUT milliseconds. */
00239 UPNPControl::UPNPError
00240 UPNPControlThread::initializeUPNP()
00241 {
00242   struct UPNPDev *devlist;
00243   int retval;
00244 
00245   memset(&urls, 0, sizeof(struct UPNPUrls));
00246   memset(&data, 0, sizeof(struct IGDdatas));
00247 
00248   UPNPControl::instance()->setState(UPNPControl::DiscoverState);
00249 
00250   devlist = upnpDiscover(UPNPCONTROL_DISCOVER_TIMEOUT, NULL, NULL, 0);
00251   if (NULL == devlist) {
00252     vWarn("upnpDiscover returned: NULL");
00253     return UPNPControl::NoUPNPDevicesFound;
00254   }
00255 
00256   retval = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
00257 
00258   vInfo("GetValidIGD returned: %1").arg(retval);
00259 
00260   freeUPNPDevlist(devlist);
00261 
00262   if (retval != 1 && retval != 2)
00263     return UPNPControl::NoValidIGDsFound;
00264 
00265   return UPNPControl::Success;
00266 }
00267 
00268 /** Adds a port forwarding mapping from external:<b>port</b> to
00269  * internal:<b>port</b>. Returns 0 on success, or non-zero on failure. */
00270 UPNPControl::UPNPError
00271 UPNPControlThread::forwardPort(quint16 port)
00272 {
00273   QString sPort;
00274   int retval;
00275 
00276   char intClient[16];
00277   char intPort[6];
00278 
00279   // Convert the port number to a string
00280   sPort = QString::number(port);
00281 
00282   // Add the port mapping of external:port -> internal:port
00283   retval = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
00284                                qPrintable(sPort), qPrintable(sPort), lanaddr,
00285                                "Tor relay", "TCP", NULL);
00286   if(UPNPCOMMAND_SUCCESS != retval) {
00287     vWarn("AddPortMapping(%1, %2, %3) failed with code %4")
00288             .arg(sPort).arg(sPort).arg(lanaddr).arg(retval);
00289     return UPNPControl::AddPortMappingFailed;
00290   }
00291 
00292   // Check if the port mapping was accepted
00293   retval = UPNP_GetSpecificPortMappingEntry(urls.controlURL, data.first.servicetype,
00294                                             qPrintable(sPort), "TCP",
00295                                             intClient, intPort);
00296   if(UPNPCOMMAND_SUCCESS != retval) {
00297     vWarn("GetSpecificPortMappingEntry() failed with code %1").arg(retval);
00298     return UPNPControl::GetPortMappingFailed;
00299   }
00300 
00301   if(! intClient[0]) {
00302     vWarn("GetSpecificPortMappingEntry failed.");
00303     return UPNPControl::GetPortMappingFailed;
00304   }
00305 
00306   // Output the mapping
00307   vInfo("(external):%1 -> %2:%3").arg(sPort).arg(intClient).arg(intPort);
00308 
00309   return UPNPControl::Success;
00310 }
00311 
00312 /** Removes the port mapping for <b>port</b>. Returns 0 on success or non-zero
00313  * on failure. */
00314 UPNPControl::UPNPError
00315 UPNPControlThread::disablePort(quint16 port)
00316 {
00317   QString sPort = QString::number(port);
00318 
00319   // Remove the mapping
00320   int retval = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype,
00321                                       qPrintable(sPort), "TCP", NULL);
00322   if(UPNPCOMMAND_SUCCESS != retval) {
00323     vWarn("DeletePortMapping() failed with code %1").arg(retval);
00324     return UPNPControl::DeletePortMappingFailed;
00325   }
00326 
00327   // Output the cancelled mapping
00328   vInfo("(external):%1 -> <>").arg(sPort);
00329 
00330   return UPNPControl::Success;
00331 }
00332 
Generated on Mon Aug 30 23:09:51 2010 for Vidalia by  doxygen 1.6.3