Vidalia 0.3.1
UPNPControlThread.cpp
Go to the documentation of this file.
1/*
2** This file is part of Vidalia, and is subject to the license terms in the
3** LICENSE file, found in the top level directory of this distribution. If
4** you did not receive the LICENSE file with this file, you may obtain it
5** from the Vidalia source package distributed by the Vidalia Project at
6** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7** including this file, may be copied, modified, propagated, or distributed
8** except according to the terms described in the LICENSE file.
9*/
10
11/*
12** \file UPNPControlThread.cpp
13** \brief Thread for configuring UPnP in the background
14*/
15
16#include "UPNPControlThread.h"
17#include "UPNPControl.h"
18#include "Vidalia.h"
19
20#include <QWaitCondition>
21#include <QMutex>
22#include <QTime>
23#include <QTextStream>
24#include <QString>
25#include <QMessageBox>
26
27#define UPNPCONTROL_REINIT_MSEC 300000 // 5 minutes
28#define UPNPCONTROL_MAX_WAIT_MSEC 60000 // 1 minute
29
30
31/** Constructor. <b>control</b> will be used for retrieving the desired port
32 * forwarding state. */
34{
35 _upnpInitialized = QTime();
36 _keepRunning = true;
37 _control = control;
38
39 _dirPort = 0;
40 _orPort = 0;
41
42 _waitCondition = new QWaitCondition();
43 _waitMutex = new QMutex();
44}
45
46/** Destructor. The UPnP control thread must be stopped prior to destroying
47 * this object.
48 * \sa stop()
49 */
51{
52 delete _waitCondition;
53 delete _waitMutex;
54}
55
56/** Thread entry point. The thread has a main loop that periodically wakes up
57 * and updates the configured port mappings. Upon exiting, all port mappings
58 * will be removed. */
59void
61{
62 bool shouldExit = false;
63
64 forever {
65 /* TODO: Check for switching OR/Dir port */
66 /* TODO: Check for router losing state */
67
69
70 /* Wait for something to happen */
71 _waitMutex->lock();
72 if (_keepRunning) {
73 /* We should continue */
76
77 /* Maybe we were asked to exit while waiting */
78 shouldExit = !_keepRunning;
79 _waitMutex->unlock();
80 if (shouldExit)
81 break;
82 } else {
83 /* We should exit */
84 _waitMutex->unlock();
85 break;
86 }
87 }
88
89 /* Remove the existing port forwards */
92}
93
94/** Sets up port forwarding according the previously-configured desired state.
95 * The desired state is set using UPNPControl's setDesiredState() method.
96 * \sa UPNPControl::setDesiredState
97 */
98void
100{
101 quint16 desiredDirPort, desiredOrPort;
102 bool force_init = false;
104
105 /* Get desired state */
106 _control->getDesiredState(&desiredDirPort, &desiredOrPort);
107
108 /* If it's been a while since we initialized the router, or time has gone
109 backward, then maybe the router has gone away or forgotten the forwards.
110 Reset UPnP state, and re-do the port forwarding */
111 if (_upnpInitialized.isNull() || // Is this the first time we have used UPNP?
112 _upnpInitialized>QTime::currentTime() || // Has time gone backwards?
113 _upnpInitialized.addMSecs(UPNPCONTROL_REINIT_MSEC)<QTime::currentTime() // Has it been REINIT_MSEC since initialization
114 ) {
115 _upnpInitialized = QTime();
116 force_init = true;
117 }
118
119 if (!force_init) {
120 /* Configure DirPort */
121 if (desiredDirPort != _dirPort) {
123 retval = updatePort(_dirPort, desiredDirPort);
124 if (retval == UPNPControl::Success)
125 _dirPort = desiredDirPort;
126 else
127 goto err;
128 }
129
130 /* Configure ORPort */
131 if (desiredOrPort != _orPort) {
133 retval = updatePort(_orPort, desiredOrPort);
134 if (retval == UPNPControl::Success)
135 _orPort = desiredOrPort;
136 else
137 goto err;
138 }
139 } else {
140 /* Add the mapping even if they exist already */
142 retval = updatePort(0, desiredDirPort);
143 if (retval == UPNPControl::Success)
144 _dirPort = desiredDirPort;
145 else
146 goto err;
147
149 retval = updatePort(0, desiredOrPort);
150 if (retval == UPNPControl::Success)
151 _orPort = desiredOrPort;
152 else goto err;
153 }
154
156 return;
157
158err:
161}
162
163/** Terminates the UPnP control thread's run() loop.
164 * \sa run()
165 */
166void
168{
169 /* Lock access to _keepRunning */
170 _waitMutex->lock();
171
172 /* Ask the thread to stop */
173 _keepRunning = false;
174
175 /* Wake it up if needed */
176 _waitCondition->wakeAll();
177
178 /* Unlock shared state */
179 _waitMutex->unlock();
180
181 /* Wait for it to finish */
182 wait();
183}
184
185/** Wakes up the UPnP control thread's run() loop.
186 * \sa run()
187 */
188void
190{
191 _waitMutex->lock();
192 _waitCondition->wakeAll();
193 _waitMutex->unlock();
194}
195
196/** Updates the port mapping for <b>oldPort</b>, changing it to
197 * <b>newPort</b>. */
199UPNPControlThread::updatePort(quint16 oldPort, quint16 newPort)
200{
202
203#ifdef Q_OS_WIN32
204 // Workaround from http://trolltech.com/developer/knowledgebase/579
205 WSAData wsadata;
206 if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) {
207 vWarn("WSAStartup failure while updating UPnP port forwarding");
209 }
210#endif
211
212 if (_upnpInitialized.isNull() && (oldPort != 0 || newPort != 0)) {
213 retval = initializeUPNP();
214 if (retval == UPNPControl::Success)
215 _upnpInitialized = QTime::currentTime();
216 else
217 _upnpInitialized = QTime();
218 } else {
219 retval = UPNPControl::Success;
220 }
221
222 if (retval == UPNPControl::Success && oldPort != 0)
223 retval = disablePort(oldPort);
224
225 if (retval == UPNPControl::Success && newPort != 0)
226 retval = forwardPort(newPort);
227
228#ifdef Q_OS_WIN32
229 WSACleanup();
230#endif
231
232 return retval;
233}
234
235/** Discovers UPnP-enabled IGDs on the network. Based on
236 * http://miniupnp.free.fr/files/download.php?file=xchat-upnp20061022.patch
237 * This method will block for UPNPCONTROL_DISCOVER_TIMEOUT milliseconds. */
240{
241 struct UPNPDev *devlist;
242 int retval;
243
244 memset(&urls, 0, sizeof(struct UPNPUrls));
245 memset(&data, 0, sizeof(struct IGDdatas));
246
248
249 devlist = upnpDiscover(UPNPCONTROL_DISCOVER_TIMEOUT, NULL, NULL, 0);
250 if (NULL == devlist) {
251 vWarn("upnpDiscover returned: NULL");
253 }
254
255 retval = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
256
257 vInfo("GetValidIGD returned: %1").arg(retval);
258
259 freeUPNPDevlist(devlist);
260
261 if (retval != 1 && retval != 2)
263
265}
266
267/** Adds a port forwarding mapping from external:<b>port</b> to
268 * internal:<b>port</b>. Returns 0 on success, or non-zero on failure. */
271{
272 QString sPort;
273 int retval;
274
275 char intClient[16];
276 char intPort[6];
277
278 // Convert the port number to a string
279 sPort = QString::number(port);
280
281 // Add the port mapping of external:port -> internal:port
283 qPrintable(sPort), qPrintable(sPort), lanaddr,
284 "Tor relay", "TCP", NULL);
285 if(UPNPCOMMAND_SUCCESS != retval) {
286 vWarn("AddPortMapping(%1, %2, %3) failed with code %4")
287 .arg(sPort).arg(sPort).arg(lanaddr).arg(retval);
289 }
290
291 // Check if the port mapping was accepted
293 qPrintable(sPort), "TCP",
294 intClient, intPort);
295 if(UPNPCOMMAND_SUCCESS != retval) {
296 vWarn("GetSpecificPortMappingEntry() failed with code %1").arg(retval);
298 }
299
300 if(! intClient[0]) {
301 vWarn("GetSpecificPortMappingEntry failed.");
303 }
304
305 // Output the mapping
306 vInfo("(external):%1 -> %2:%3").arg(sPort).arg(intClient).arg(intPort);
307
309}
310
311/** Removes the port mapping for <b>port</b>. Returns 0 on success or non-zero
312 * on failure. */
315{
316 QString sPort = QString::number(port);
317
318 // Remove the mapping
320 qPrintable(sPort), "TCP", NULL);
321 if(UPNPCOMMAND_SUCCESS != retval) {
322 vWarn("DeletePortMapping() failed with code %1").arg(retval);
324 }
325
326 // Output the cancelled mapping
327 vInfo("(external):%1 -> <>").arg(sPort);
328
330}
331
#define UPNPCONTROL_REINIT_MSEC
#define UPNPCONTROL_MAX_WAIT_MSEC
#define vWarn(fmt)
Definition: Vidalia.h:42
#define vInfo(fmt)
Definition: Vidalia.h:40
void setState(UPNPState state)
static UPNPControl * instance()
Definition: UPNPControl.cpp:31
void getDesiredState(quint16 *desiredDirPort, quint16 *desiredOrPort)
Definition: UPNPControl.cpp:78
void setError(UPNPError error)
@ AddPortMappingFailed
Definition: UPNPControl.h:37
@ GetPortMappingFailed
Definition: UPNPControl.h:38
@ NoUPNPDevicesFound
Definition: UPNPControl.h:34
@ DeletePortMappingFailed
Definition: UPNPControl.h:39
@ NoValidIGDsFound
Definition: UPNPControl.h:35
@ WSAStartupFailed
Definition: UPNPControl.h:36
@ UpdatingDirPortState
Definition: UPNPControl.h:48
@ UpdatingORPortState
Definition: UPNPControl.h:47
@ ForwardingCompleteState
Definition: UPNPControl.h:49
UPNPControl::UPNPError disablePort(quint16 port)
struct IGDdatas data
struct UPNPUrls urls
UPNPControl * _control
UPNPControl::UPNPError initializeUPNP()
QWaitCondition * _waitCondition
UPNPControl::UPNPError updatePort(quint16 oldPort, quint16 newPort)
UPNPControlThread(UPNPControl *control)
UPNPControl::UPNPError forwardPort(quint16 port)
static const int UPNPCONTROL_DISCOVER_TIMEOUT
LIBSPEC void freeUPNPDevlist(struct UPNPDev *devlist)
LIBSPEC struct UPNPDev * upnpDiscover(int delay, const char *multicastif, const char *minissdpdsock, int sameport)
LIBSPEC int UPNP_GetValidIGD(struct UPNPDev *devlist, struct UPNPUrls *urls, struct IGDdatas *data, char *lanaddr, int lanaddrlen)
bool err(QString *str, const QString &errmsg)
Definition: stringutil.cpp:37
char servicetype[MINIUPNPC_URL_MAXSIZE]
struct IGDdatas_service first
char * controlURL
Definition: miniupnpc.h:62
#define UPNPCOMMAND_SUCCESS
Definition: upnpcommands.h:14
LIBSPEC int UPNP_AddPortMapping(const char *controlURL, const char *servicetype, const char *extPort, const char *inPort, const char *inClient, const char *desc, const char *proto, const char *remoteHost)
LIBSPEC int UPNP_GetSpecificPortMappingEntry(const char *controlURL, const char *servicetype, const char *extPort, const char *proto, char *intClient, char *intPort)
LIBSPEC int UPNP_DeletePortMapping(const char *controlURL, const char *servicetype, const char *extPort, const char *proto, const char *remoteHost)