Vidalia 0.3.1
ControlConnection.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 ControlConnection.cpp
13** \brief A connection to Tor's control interface, responsible for sending and
14** receiving commands and events
15*/
16
17#include "ControlConnection.h"
18#include "tcglobal.h"
19#include "stringutil.h"
20
21#include <QCoreApplication>
22#include <QMutexLocker>
23
24/** Maximum number of times we'll try to connect to Tor before giving up.*/
25#define MAX_CONNECT_ATTEMPTS 5
26/** Time to wait between control connection attempts (in milliseconds). */
27#define CONNECT_RETRY_DELAY 2*1000
28
29
30/** Default constructor. */
32{
33 _events = events;
34 _status = Unset;
35 _sock = 0;
37 _method = method;
38}
39
40/** Destructor. */
42{
43 /* Exit the event loop */
44 exit();
45 /* Wait for the thread to finish */
46 wait();
47 /* Clean up after the send waiter */
48 delete _sendWaiter;
49}
50
51/** Connect to the specified Tor control interface. */
52void
53ControlConnection::connect(const QHostAddress &addr, quint16 port)
54{
55 if (isRunning()) {
56 tc::error("Bug: Tried to call ControlConnection::connect() when the "
57 "control thread is already running.");
58 return;
59 }
60
61 /* Save the destination information */
62 _addr = addr;
63 _port = port;
64 _sock = 0;
67
68 /* Kick off the thread in which the control socket will live */
69 QThread::start();
70}
71
72/** Connect to the specified Tor control socket interface. */
73void
74ControlConnection::connect(const QString &addr)
75{
76 if (isRunning()) {
77 tc::error("Bug: Tried to call ControlConnection::connect() when the "
78 "control thread is already running.");
79 return;
80 }
81
82 _path = addr;
85
86 /* Kick off the thread in which the control socket will live */
87 QThread::start();
88}
89
90/** Attempt to establish a connection to Tor's control interface. We will try
91 * a maximum of MAX_CONNECT_ATTEMPTS, waiting CONNECT_RETRY_DELAY between each
92 * attempt, to give slow Tors a chance to finish binding their control port. */
93void
95{
97 tc::debug("Connecting to Tor (Attempt %1 of %2)").arg(_connectAttempt)
99
100 _connMutex.lock();
101 switch(_method) {
104 break;
105
106 default:
109 break;
110 }
111 _connMutex.unlock();
112}
113
114/** Disconnect from Tor's control interface. */
115void
117{
119 _connMutex.lock();
120 switch(_method) {
123 break;
124
125 default:
128 break;
129 }
130 _connMutex.unlock();
131}
132
133/** Called when the control socket is connected. This method checks that the
134 * control protocol version of the Tor we connected to is at least V1. */
135void
137{
139 emit connected();
140}
141
142/** Called when the control socket is disconnected and stops the control
143 * thread's event loop. */
144void
146{
148 emit disconnected();
149 exit(0);
150}
151
152/** Called when the control socket encounters <b>error</b>. */
153void
154ControlConnection::onError(QAbstractSocket::SocketError error)
155{
156 if (status() == Connecting) {
157 /* If we got a 'connection refused' and we haven't exceeded
158 * MAX_CONNECT_ATTEMPTS, then try to reconnect since Tor is probably
159 * running, but it doesn't have a ControlSocket open yet. */
160 if (error == QAbstractSocket::ConnectionRefusedError &&
162 tc::debug("Control connection refused. Retrying in %1ms.")
165 } else {
166 /* Exceeded maximum number of connect attempts. Give up. */
167 QString errstr = ControlSocket::toString(error);
168 tc::error("Vidalia was unable to connect to Tor: %1").arg(errstr);
169 emit connectFailed(tr("Vidalia was unable to connect to Tor. (%1)")
170 .arg(errstr));
172 }
173 } else if (error == QAbstractSocket::RemoteHostClosedError) {
174 /* Tor closed the connection. This is common when we send a 'shutdown' or
175 * 'halt' signal to Tor and doesn't need to be logged as loudly. */
176 tc::warn("Tor closed the control connection.");
177 } else {
178 /* Some other error. */
179 /*XXX We may want to be emitting these so the GUI thread can learn about
180 * them and display an error message. */
181 tc::error("Control socket error: %1").arg(ControlSocket::toString(error));
182 }
183}
184
185/** Cancels a pending control connection to Tor. */
186void
188{
189 tc::warn("Control connection attempt cancelled.");
191 exit(0);
192}
193
194/** Returns true if the control socket is connected to Tor. */
195bool
197{
198 return (status() == Connected);
199}
200
201/** Returns the status of the control connection. */
204{
205 QMutexLocker locker(&_statusMutex);
206 return _status;
207}
208
209/** Returns a string description of the control Status value
210 * <b>status</b>. */
211QString
213{
214 QString str;
215 switch (status) {
216 case Unset: str = "Unset"; break;
217 case Disconnected: str = "Disconnected"; break;
218 case Disconnecting: str = "Disconnecting"; break;
219 case Connecting: str = "Connecting"; break;
220 case Connected: str = "Connected"; break;
221 default: str = "unknown";
222 }
223 return str;
224}
225
226/** Sets the control connection status. */
227void
229{
230 QMutexLocker locker(&_statusMutex);
231 tc::debug("Control connection status changed from '%1' to '%2'")
234 _status = status;
235}
236
237/** Sends a control command to Tor and waits for the reply. */
238bool
240 ControlReply &reply, QString *errmsg)
241{
242 bool result = false;
243 QString errstr;
244
245 _recvMutex.lock();
246 if (send(cmd, &errstr)) {
247 /* Create and enqueue a new receive waiter */
248 ReceiveWaiter *w = new ReceiveWaiter();
249 _recvQueue.enqueue(w);
250 _recvMutex.unlock();
251
252 /* Wait for and get the result, clean up, and return */
253 result = w->getResult(&reply, &errstr);
254 if (!result)
255 tc::error("Failed to receive control reply: %1").arg(errstr);
256 delete w;
257 } else {
258 tc::error("Failed to send control command (%1): %2").arg(cmd.keyword())
259 .arg(errstr);
260 _recvMutex.unlock();
261 }
262
263 if (!result && errmsg)
264 *errmsg = errstr;
265 return result;
266}
267
268/** Sends a control command to Tor and returns true if the command was sent
269 * successfully. Otherwise, returns false and <b>*errmsg</b> (if supplied)
270 * will be set. */
271bool
272ControlConnection::send(const ControlCommand &cmd, QString *errmsg)
273{
274 _connMutex.lock();
275 if (!_sock || !_sock->isConnected()) {
276 _connMutex.unlock();
277 return err(errmsg, tr("Control socket is not connected."));
278 }
279 QCoreApplication::postEvent(_sock, new SendCommandEvent(cmd, _sendWaiter));
280 _connMutex.unlock();
281
282 return _sendWaiter->getResult(errmsg);
283}
284
285/** Called when there is data on the control socket. */
286void
288{
289 QMutexLocker locker(&_connMutex);
290 ReceiveWaiter *waiter;
291 QString errmsg;
292
293 while (_sock->canReadLine()) {
294 ControlReply reply;
295 if (_sock->readReply(reply, &errmsg)) {
296 if (reply.getStatus() == "650") {
297 /* Asynchronous event message */
298 tc::debug("Control Event: %1").arg(reply.toString());
299
300 if (_events) {
301 _events->handleEvent(reply);
302 }
303 } else {
304 /* Response to a previous command */
305 tc::debug("Control Reply: %1").arg(reply.toString());
306
307 _recvMutex.lock();
308 if (!_recvQueue.isEmpty()) {
309 waiter = _recvQueue.dequeue();
310 waiter->setResult(true, reply);
311 }
312 _recvMutex.unlock();
313 }
314 } else {
315 tc::error("Unable to read control reply: %1").arg(errmsg);
316 }
317 }
318}
319
320/** Main thread implementation. Creates and connects a control socket, then
321 * spins up an event loop. */
322void
324{
325 /* Create a new control socket */
326 _connMutex.lock();
328
329 _connectTimer = new QTimer();
330 _connectTimer->setSingleShot(true);
331
332 QObject::connect(_sock, SIGNAL(readyRead()), this, SLOT(onReadyRead()),
333 Qt::DirectConnection);
334 QObject::connect(_sock, SIGNAL(disconnected()), this, SLOT(onDisconnected()),
335 Qt::DirectConnection);
336 QObject::connect(_sock, SIGNAL(connected()), this, SLOT(onConnected()),
337 Qt::DirectConnection);
338 QObject::connect(_sock, SIGNAL(error(QAbstractSocket::SocketError)),
339 this, SLOT(onError(QAbstractSocket::SocketError)),
340 Qt::DirectConnection);
341 QObject::connect(_connectTimer, SIGNAL(timeout()), this, SLOT(connect()),
342 Qt::DirectConnection);
343
344 _connMutex.unlock();
345
346 /* Attempt to connect to Tor */
347 connect();
348 tc::debug("Starting control connection event loop.");
349 exec();
350 tc::debug("Exited control connection event loop.");
351
352 /* Clean up the socket */
353 _connMutex.lock();
354 _sock->disconnect(this);
355 delete _sock;
356 delete _connectTimer;
357 _sock = 0;
358 _connMutex.unlock();
359
360 /* If there are any messages waiting for a response, clear them. */
362 _sendWaiter->setResult(false, tr("Control socket is not connected."));
363
364 _recvMutex.lock();
365 while (!_recvQueue.isEmpty()) {
366 ReceiveWaiter *w = _recvQueue.dequeue();
367 w->setResult(false, ControlReply(),
368 tr("Control socket is not connected."));
369 }
370 _recvMutex.unlock();
371}
372
373
374/*
375 * ControlConnection::ReceiveWaiter
376 */
377/** Waits for and gets the reply from a control command. */
378bool
380 QString *errmsg)
381{
382 forever {
383 _mutex.lock();
384 if (_status == Waiting) {
385 _waitCond.wait(&_mutex);
386 _mutex.unlock();
387 } else {
388 _mutex.unlock();
389 break;
390 }
391 }
392 if (errmsg) {
393 *errmsg = _errmsg;
394 }
395 *reply = _reply;
396 return (_status == Success);
397}
398
399/** Sets the result and reply from a control command. */
400void
402 const ControlReply &reply,
403 const QString &errmsg)
404{
405 _mutex.lock();
406 _status = (success ? Success : Failed);
407 _reply = reply;
408 _errmsg = errmsg;
409 _mutex.unlock();
410 _waitCond.wakeAll();
411}
412
#define CONNECT_RETRY_DELAY
#define MAX_CONNECT_ATTEMPTS
stop errmsg connect(const QHostAddress &address, quint16 port)
QString keyword() const
enum ControlConnection::ReceiveWaiter::ReceiveStatus _status
bool getResult(ControlReply *reply, QString *errmsg=0)
void setResult(bool success, const ControlReply &reply, const QString &errmsg=QString())
void onError(QAbstractSocket::SocketError error)
ControlConnection(ControlMethod::Method method, TorEvents *events=0)
ControlSocket * _sock
QQueue< ReceiveWaiter * > _recvQueue
void setStatus(Status status)
void connectFailed(QString errmsg)
QString statusString(Status status)
bool send(const ControlCommand &cmd, ControlReply &reply, QString *errmsg=0)
ControlMethod::Method _method
SendCommandEvent::SendWaiter * _sendWaiter
QString toString() const
QString getStatus() const
static QString toString(const QAbstractSocket::SocketError error)
void disconnectFromHost()
void connectToHost(const QHostAddress &address, quint16 port)
bool readReply(ControlReply &reply, QString *errmsg=0)
void disconnectFromServer()
void connectToServer(const QString &name)
bool getResult(QString *errmsg=0)
void setResult(bool success, const QString &errmsg=QString())
void handleEvent(const ControlReply &reply)
Definition: TorEvents.cpp:124
DebugMessage arg(const QString &a)
Definition: tcglobal.h:48
DebugMessage warn(const QString &fmt)
Definition: tcglobal.cpp:32
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
DebugMessage debug(const QString &fmt)
Definition: tcglobal.cpp:24
bool err(QString *str, const QString &errmsg)
Definition: stringutil.cpp:37