Vidalia 0.2.10
|
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