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

Generated on Wed Dec 23 21:06:55 2009 for Vidalia by  doxygen 1.6.1