Vidalia
0.2.17
|
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.torproject.org/projects/vidalia.html. No part of Vidalia, 00007 ** including this file, may be copied, modified, propagated, or distributed 00008 ** except according to 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