Vidalia 0.2.12

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