PageRenderTime 639ms CodeModel.GetById 121ms app.highlight 416ms RepoModel.GetById 87ms app.codeStats 1ms

/src/libtomahawk/network/Servent.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 1455 lines | 1113 code | 239 blank | 103 comment | 186 complexity | e56e39e8adde5497fbb9877e950fdd36 MD5 | raw file
   1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
   2 *
   3 *   Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
   4 *   Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
   5 *   Copyright 2013,      Teo Mrnjavac <teo@kde.org>
   6 *   Copyright 2013-2014, Uwe L. Korn <uwelk@xhochy.com>
   7 *
   8 *   Tomahawk is free software: you can redistribute it and/or modify
   9 *   it under the terms of the GNU General Public License as published by
  10 *   the Free Software Foundation, either version 3 of the License, or
  11 *   (at your option) any later version.
  12 *
  13 *   Tomahawk is distributed in the hope that it will be useful,
  14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16 *   GNU General Public License for more details.
  17 *
  18 *   You should have received a copy of the GNU General Public License
  19 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  20 */
  21
  22#include "Servent_p.h"
  23
  24#include "accounts/AccountManager.h"
  25#include "database/Database.h"
  26#include "database/DatabaseImpl.h"
  27#include "network/acl/AclRegistry.h"
  28#include "network/Msg.h"
  29#include "network/ConnectionManager.h"
  30#include "network/DbSyncConnection.h"
  31#include "sip/SipInfo.h"
  32#include "sip/PeerInfo.h"
  33#include "sip/SipPlugin.h"
  34#include "utils/Closure.h"
  35#include "utils/Json.h"
  36#include "utils/TomahawkUtils.h"
  37#include "utils/Logger.h"
  38#include "utils/NetworkAccessManager.h"
  39#include "utils/NetworkReply.h"
  40
  41#include "Connection.h"
  42#include "ControlConnection.h"
  43#include "PortFwdThread.h"
  44#include "QTcpSocketExtra.h"
  45#include "Source.h"
  46#include "SourceList.h"
  47#include "StreamConnection.h"
  48#include "UrlHandler.h"
  49
  50#include <QCoreApplication>
  51#include <QMutexLocker>
  52#include <QNetworkInterface>
  53#include <QFile>
  54#include <QThread>
  55#include <QNetworkProxy>
  56#include <QNetworkRequest>
  57#include <QNetworkReply>
  58
  59
  60typedef QPair< QList< SipInfo >, Connection* > sipConnectionPair;
  61Q_DECLARE_METATYPE( sipConnectionPair )
  62Q_DECLARE_METATYPE( QList< SipInfo > )
  63Q_DECLARE_METATYPE( Connection* )
  64Q_DECLARE_METATYPE( QTcpSocketExtra* )
  65Q_DECLARE_METATYPE( Tomahawk::peerinfo_ptr )
  66
  67
  68using namespace Tomahawk;
  69
  70Servent* Servent::s_instance = 0;
  71
  72
  73Servent*
  74Servent::instance()
  75{
  76    return s_instance;
  77}
  78
  79
  80Servent::Servent( QObject* parent )
  81    : QTcpServer( parent ),
  82      d_ptr( new ServentPrivate( this ) )
  83{
  84    s_instance = this;
  85
  86    d_func()->noAuth = qApp->arguments().contains( "--noauth" );
  87
  88    setProxy( QNetworkProxy::NoProxy );
  89
  90    IODeviceFactoryFunc fac = std::bind( &Servent::remoteIODeviceFactory, this,
  91                                         std::placeholders::_1,
  92                                         std::placeholders::_2,
  93                                         std::placeholders::_3 );
  94    Tomahawk::UrlHandler::registerIODeviceFactory( "servent", fac );
  95}
  96
  97
  98Servent::~Servent()
  99{
 100    tDebug() << Q_FUNC_INFO;
 101
 102    foreach ( ControlConnection* cc, d_func()->controlconnections )
 103        delete cc;
 104
 105    if ( d_func()->portfwd )
 106    {
 107        d_func()->portfwd.data()->quit();
 108        d_func()->portfwd.data()->wait( 60000 );
 109        delete d_func()->portfwd.data();
 110    }
 111
 112    delete d_ptr;
 113}
 114
 115
 116bool
 117Servent::startListening( QHostAddress ha, bool upnp, int port, Tomahawk::Network::ExternalAddress::Mode mode, int defaultPort, bool autoDetectExternalIp, const QString& externalHost, int externalPort )
 118{
 119    Q_D( Servent );
 120
 121    d->externalAddresses = QList<QHostAddress>();
 122    d->port = port;
 123
 124    // Listen on both the selected port and, if not the same, the default port -- the latter sometimes necessary for zeroconf
 125    // TODO: only listen on both when zeroconf sip is enabled
 126    // TODO: use a real zeroconf system instead of a simple UDP broadcast?
 127    if ( !listen( ha, d->port ) )
 128    {
 129        if ( d->port != defaultPort )
 130        {
 131            if ( !listen( ha, defaultPort ) )
 132            {
 133                tLog() << Q_FUNC_INFO << "Failed to listen on both port" << d->port << "and port" << defaultPort;
 134                tLog() << Q_FUNC_INFO << "Error string is:" << errorString();
 135                return false;
 136            }
 137            else
 138                d->port = defaultPort;
 139        }
 140    }
 141
 142    d->externalListenAll = false;
 143
 144    if ( ha == QHostAddress::Any || ha == QHostAddress::AnyIPv6 )
 145    {
 146        // We are listening on all available addresses, so we should send a SipInfo for all of them.
 147        d->externalAddresses = QNetworkInterface::allAddresses();
 148        cleanAddresses( d->externalAddresses );
 149        tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Listening to" << d->externalAddresses;
 150        d->externalListenAll = true;
 151    }
 152    else if ( ( ha.toString() != "127.0.0.1" ) && ( ha.toString() != "::1" ) && ( ha.toString() != "::7F00:1" ) )
 153    {
 154        // We listen only to one specific Address, only announce this.
 155        d->externalAddresses.append( ha );
 156    }
 157    // If we only accept connections via localhost, we'll announce nothing.
 158
 159    tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Servent listening on port" << d->port
 160                       << "using address" << ha.toString()
 161                       << "- servent thread:" << thread()
 162                       << "- address mode:" << (int)( mode );
 163
 164    switch ( mode )
 165    {
 166        case Tomahawk::Network::ExternalAddress::Static:
 167            d->externalPort = externalPort;
 168            if ( autoDetectExternalIp )
 169            {
 170                QNetworkReply* reply = Tomahawk::Utils::nam()->get( QNetworkRequest( QUrl( "http://toma.hk/?stat=1" ) ) );
 171                connect( reply, SIGNAL( finished() ), SLOT( ipDetected() ) );
 172                // Not emitting ready here as we are not done.
 173            }
 174            else
 175            {
 176                d->externalHostname = externalHost;
 177                d->ready = true;
 178                // All setup is made, were done.
 179                emit ready();
 180            }
 181            break;
 182
 183        case Tomahawk::Network::ExternalAddress::Lan:
 184            // Nothing has to be done here.
 185            d->ready = true;
 186            emit ready();
 187            break;
 188
 189        case Tomahawk::Network::ExternalAddress::Upnp:
 190            if ( upnp )
 191            {
 192                // upnp could be turned off on the cli with --noupnp
 193                tLog( LOGVERBOSE ) << Q_FUNC_INFO << "External address mode set to upnp...";
 194                d->portfwd = QPointer< PortFwdThread >( new PortFwdThread( d->port ) );
 195                Q_ASSERT( d->portfwd );
 196                connect( d->portfwd.data(), SIGNAL( externalAddressDetected( QHostAddress, unsigned int ) ),
 197                                      SLOT( setExternalAddress( QHostAddress, unsigned int ) ) );
 198                d->portfwd.data()->start();
 199            }
 200            else
 201            {
 202                d->ready = true;
 203                emit ready();
 204            }
 205            break;
 206    }
 207
 208    connect( ACLRegistry::instance(), SIGNAL( aclResult( QString, QString, Tomahawk::ACLStatus::Type ) ),
 209             this, SLOT( checkACLResult( QString, QString, Tomahawk::ACLStatus::Type ) ),
 210             Qt::QueuedConnection );
 211
 212    return true;
 213}
 214
 215
 216void
 217Servent::setExternalAddress( QHostAddress ha, unsigned int port )
 218{
 219    if ( isValidExternalIP( ha ) )
 220    {
 221        d_func()->externalHostname = ha.toString();
 222        d_func()->externalPort = port;
 223    }
 224
 225    if ( d_func()->externalPort == 0 || !isValidExternalIP( ha ) )
 226        tLog() << Q_FUNC_INFO << "UPnP failed, no further external address could be acquired!";
 227    else
 228        tLog( LOGVERBOSE ) << Q_FUNC_INFO << "UPnP setup successful";
 229
 230    d_func()->ready = true;
 231    emit ready();
 232}
 233
 234
 235QString
 236Servent::createConnectionKey( const QString& name, const QString &nodeid, const QString &key, bool onceOnly )
 237{
 238    Q_ASSERT( this->thread() == QThread::currentThread() );
 239
 240    QString _key = ( key.isEmpty() ? uuid() : key );
 241    ControlConnection* cc = new ControlConnection( this );
 242    cc->setName( name.isEmpty() ? QString( "KEY(%1)" ).arg( key ) : name );
 243    if ( !nodeid.isEmpty() )
 244        cc->setId( nodeid );
 245    cc->setOnceOnly( onceOnly );
 246
 247    tDebug( LOGVERBOSE ) << "Creating connection key with name of" << cc->name() << "and id of" << cc->id() << "and key of" << _key << "; key is once only? :" << (onceOnly ? "true" : "false");
 248    registerOffer( _key, cc );
 249    return _key;
 250}
 251
 252
 253bool
 254Servent::isValidExternalIP( const QHostAddress& addr )
 255{
 256    QString ip = addr.toString();
 257    if (addr.protocol() == QAbstractSocket::IPv4Protocol)
 258    {
 259        // private network
 260        if ( addr.isInSubnet(QHostAddress::parseSubnet( "10.0.0.0/8" ) ) )
 261            return false;
 262        // localhost
 263        if ( addr.isInSubnet(QHostAddress::parseSubnet( "127.0.0.0/8" ) ) )
 264            return false;
 265        // private network
 266        if ( addr.isInSubnet(QHostAddress::parseSubnet( "169.254.0.0/16" ) ) )
 267            return false;
 268        // private network
 269        if ( addr.isInSubnet(QHostAddress::parseSubnet( "172.16.0.0/12" ) ) )
 270            return false;
 271        // private network
 272        if ( addr.isInSubnet(QHostAddress::parseSubnet( "192.168.0.0/16" ) ) )
 273            return false;
 274        // multicast
 275        if ( addr.isInSubnet(QHostAddress::parseSubnet( "224.0.0.0/4" ) ) )
 276            return false;
 277    }
 278    else if (addr.protocol() == QAbstractSocket::IPv6Protocol)
 279    {
 280        // "unspecified address"
 281        if ( addr.isInSubnet(QHostAddress::parseSubnet( "::/128" ) ) )
 282            return false;
 283        // link-local
 284        if ( addr.isInSubnet(QHostAddress::parseSubnet( "fe80::/10" ) ) )
 285            return false;
 286        // unique local addresses
 287        if ( addr.isInSubnet(QHostAddress::parseSubnet( "fc00::/7" ) ) )
 288            return false;
 289        // benchmarking only
 290        if ( addr.isInSubnet(QHostAddress::parseSubnet( "2001:2::/48" ) ) )
 291            return false;
 292        // non-routed IPv6 addresses used for Cryptographic Hash Identifiers
 293        if ( addr.isInSubnet(QHostAddress::parseSubnet( "2001:10::/28" ) ) )
 294            return false;
 295        // documentation prefix
 296        if ( addr.isInSubnet(QHostAddress::parseSubnet( "2001:db8::/32" ) ) )
 297            return false;
 298        // multicast
 299        if ( addr.isInSubnet(QHostAddress::parseSubnet( "ff00::0/8" ) ) )
 300            return false;
 301    }
 302    else
 303    {
 304        return false;
 305    }
 306
 307    return !addr.isNull();
 308}
 309
 310
 311void
 312Servent::registerOffer( const QString& key, Connection* conn )
 313{
 314    d_func()->offers[key] = QPointer<Connection>(conn);
 315}
 316
 317
 318void
 319Servent::registerLazyOffer(const QString &key, const peerinfo_ptr &peerInfo, const QString &nodeid, const int timeout )
 320{
 321    d_func()->lazyoffers[key] = QPair< peerinfo_ptr, QString >( peerInfo, nodeid );
 322    QTimer* timer = new QTimer( this );
 323    timer->setInterval( timeout );
 324    timer->setSingleShot( true );
 325    NewClosure( timer, SIGNAL( timeout() ), this, SLOT( deleteLazyOffer( const QString& ) ), key );
 326    timer->start();
 327}
 328
 329
 330void
 331Servent::deleteLazyOffer( const QString& key )
 332{
 333    d_func()->lazyoffers.remove( key );
 334
 335    // Cleanup.
 336    QTimer* timer = (QTimer*)sender();
 337    if ( timer )
 338    {
 339        timer->deleteLater();
 340    }
 341}
 342
 343
 344void
 345Servent::registerControlConnection( ControlConnection* conn )
 346{
 347    Q_D( Servent );
 348    Q_ASSERT( conn );
 349
 350    QMutexLocker locker( &d->controlconnectionsMutex );
 351    tLog( LOGVERBOSE ) << Q_FUNC_INFO << conn->name();
 352    d->controlconnections << conn;
 353    d->connectedNodes << conn->id();
 354}
 355
 356
 357void
 358Servent::unregisterControlConnection( ControlConnection* conn )
 359{
 360    Q_D( Servent );
 361    Q_ASSERT( conn );
 362
 363    QMutexLocker locker( &d->controlconnectionsMutex );
 364    tLog( LOGVERBOSE ) << Q_FUNC_INFO << conn->name();
 365    d->connectedNodes.removeAll( conn->id() );
 366    d->controlconnections.removeAll( conn );
 367}
 368
 369
 370ControlConnection*
 371Servent::lookupControlConnection( const SipInfo& sipInfo )
 372{
 373    Q_D( Servent );
 374    QMutexLocker locker( &d->controlconnectionsMutex );
 375
 376    foreach ( ControlConnection* c, d_func()->controlconnections )
 377    {
 378        tLog() << sipInfo.port() << c->peerPort() << sipInfo.host() << c->peerIpAddress().toString();
 379        if ( sipInfo.port() == c->peerPort() && sipInfo.host() == c->peerIpAddress().toString() )
 380             return c;
 381    }
 382
 383    return NULL;
 384}
 385
 386
 387ControlConnection*
 388Servent::lookupControlConnection( const QString& nodeid )
 389{
 390    Q_D( Servent );
 391    QMutexLocker locker( &d->controlconnectionsMutex );
 392
 393    foreach ( ControlConnection* c, d_func()->controlconnections )
 394    {
 395        if ( c->id() == nodeid )
 396             return c;
 397    }
 398    return NULL;
 399}
 400
 401
 402QList<SipInfo>
 403Servent::getLocalSipInfos( const QString& nodeid, const QString& key )
 404{
 405    Q_D( Servent );
 406
 407    QList<SipInfo> sipInfos = QList<SipInfo>();
 408    QList<QHostAddress> addresses = d->externalAddresses;
 409
 410    if ( d->externalListenAll )
 411    {
 412        addresses = QNetworkInterface::allAddresses();
 413        cleanAddresses( addresses );
 414    }
 415
 416    foreach ( QHostAddress ha, addresses )
 417    {
 418        SipInfo info = SipInfo();
 419        info.setHost( ha.toString() );
 420        info.setPort( d_func()->port );
 421        info.setKey( key );
 422        info.setVisible( true );
 423        info.setNodeId( nodeid );
 424        sipInfos.append( info );
 425    }
 426    if ( !d_func()->externalHostname.isEmpty() )
 427    {
 428        SipInfo info = SipInfo();
 429        info.setHost( d_func()->externalHostname );
 430        info.setPort( d_func()->externalPort );
 431        info.setKey( key );
 432        info.setVisible( true );
 433        info.setNodeId( nodeid );
 434        sipInfos.append( info );
 435    }
 436
 437    if ( sipInfos.isEmpty() )
 438    {
 439        // We are not visible via any IP, send a dummy SipInfo
 440        SipInfo info = SipInfo();
 441        info.setVisible( false );
 442        info.setKey( key );
 443        info.setNodeId( nodeid );
 444        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Only accepting connections, no usable IP to listen to found.";
 445    }
 446
 447    return sipInfos;
 448}
 449
 450
 451void
 452Servent::queueForAclResult( const QString& username, const QSet<peerinfo_ptr>& peerInfos )
 453{
 454    if ( peerInfos.isEmpty() || (*peerInfos.begin())->sipInfos().isEmpty() )
 455    {
 456        // If all peerInfos disappeared, do not queue.
 457        // If the peerInfo has not got a sipInfo anymore, do not queue either.
 458        return;
 459    }
 460
 461    if ( !d_func()->queuedForACLResult.contains( username ) )
 462    {
 463        d_func()->queuedForACLResult[username] = QMap<QString, QSet<Tomahawk::peerinfo_ptr> >();
 464    }
 465    d_func()->queuedForACLResult[username][ (*peerInfos.begin())->nodeId() ] = QSet<Tomahawk::peerinfo_ptr>( peerInfos );
 466}
 467
 468
 469SipInfo
 470Servent::getSipInfoForOldVersions( const QList<SipInfo>& sipInfos )
 471{
 472    SipInfo info = SipInfo();
 473    info.setVisible( false );
 474    foreach ( SipInfo _info, sipInfos )
 475    {
 476        QHostAddress ha = QHostAddress( _info.host() );
 477        if ( ( Servent::isValidExternalIP( ha ) && ha.protocol() == QAbstractSocket::IPv4Protocol ) || ( ha.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol ) || ( ha.isNull() && !_info.host().isEmpty() ))
 478        {
 479            info = _info;
 480            break;
 481        }
 482    }
 483
 484    return info;
 485}
 486
 487
 488void
 489Servent::registerPeer( const Tomahawk::peerinfo_ptr& peerInfo )
 490{
 491    if ( peerInfo->hasControlConnection() )
 492    {
 493        peerInfoDebug( peerInfo ) << "already had control connection, doing nothing: " << peerInfo->controlConnection()->name();
 494        tLog() << "existing control connection has following peers:";
 495        foreach ( const peerinfo_ptr& otherPeerInfo, peerInfo->controlConnection()->peerInfos() )
 496        {
 497            peerInfoDebug( otherPeerInfo );
 498        }
 499
 500        tLog() << "end peers";
 501        return;
 502    }
 503
 504    if ( peerInfo->type() == Tomahawk::PeerInfo::Local )
 505    {
 506        peerInfoDebug(peerInfo) << "we need to establish the connection now... thinking";
 507        if ( !connectedToSession( peerInfo->nodeId() ) )
 508        {
 509            handleSipInfo( peerInfo );
 510        }
 511        else
 512        {
 513//            FIXME: do we need to port this?!
 514
 515//            qDebug() << "Already connected to" << host; // so peerInfo was 0 before
 516//            qDebug() << "They connected to us and we don't have a PeerInfo object, created one...";
 517//            m_peersOnline.append( peerInfo );
 518
 519//            // attach to control connection
 520//            ControlConnection* conn = Servent::instance()->lookupControlConnection( sipInfo );
 521
 522//            // we're connected to this nodeid, so we should find a control connection for this sipinfo, no?
 523//            Q_ASSERT( conn );
 524
 525//            conn->addPeerInfo( peerInfo );
 526        }
 527    }
 528    else
 529    {
 530        QString key = uuid();
 531        const QString& nodeid = Database::instance()->impl()->dbid();
 532
 533        QList<SipInfo> sipInfos = getLocalSipInfos( nodeid, key );
 534        // The offer should be removed after some time or we will build up a heap of unused PeerInfos
 535        registerLazyOffer( key, peerInfo, nodeid, sipInfos.length() * 1.5 * CONNECT_TIMEOUT );
 536        // SipInfos were single-value before 0.7.100
 537        if ( !peerInfo->versionString().isEmpty() && TomahawkUtils::compareVersionStrings( peerInfo->versionString().split(' ').last(), "0.7.100" ) < 0)
 538        {
 539            SipInfo info = getSipInfoForOldVersions( sipInfos );
 540            peerInfo->sendLocalSipInfos( QList<SipInfo>() << info );
 541        }
 542        else
 543        {
 544            peerInfo->sendLocalSipInfos( sipInfos );
 545        }
 546
 547        handleSipInfo( peerInfo );
 548        connect( peerInfo.data(), SIGNAL( sipInfoChanged() ), SLOT( onSipInfoChanged() ) );
 549    }
 550}
 551
 552
 553void
 554Servent::onSipInfoChanged()
 555{
 556    Tomahawk::PeerInfo* peerInfo = qobject_cast< Tomahawk::PeerInfo* >( sender() );
 557
 558    if ( !peerInfo )
 559        return;
 560
 561    handleSipInfo( peerInfo->weakRef().toStrongRef() );
 562}
 563
 564
 565void
 566Servent::handleSipInfo( const Tomahawk::peerinfo_ptr& peerInfo )
 567{
 568    // We do not have received the initial SipInfo for this client yet, so wait for it.
 569    // Each client will have at least one non-visible SipInfo
 570    if ( peerInfo->sipInfos().isEmpty() )
 571        return;
 572
 573    QSharedPointer<ConnectionManager> manager = ConnectionManager::getManagerForNodeId( peerInfo->nodeId() );
 574    manager->handleSipInfo( peerInfo );
 575}
 576
 577
 578void
 579Servent::incomingConnection( qintptr sd )
 580{
 581    Q_ASSERT( this->thread() == QThread::currentThread() );
 582
 583    QTcpSocketExtra* sock = new QTcpSocketExtra;
 584    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Accepting connection, sock" << sock;
 585
 586    sock->moveToThread( thread() );
 587    sock->_disowned = false;
 588    sock->_outbound = false;
 589    if ( !sock->setSocketDescriptor( sd ) )
 590    {
 591        Q_ASSERT( false );
 592        return;
 593    }
 594
 595    connect( sock, SIGNAL( readyRead() ), SLOT( readyRead() ) );
 596    connect( sock, SIGNAL( disconnected() ), sock, SLOT( deleteLater() ) );
 597}
 598
 599
 600void
 601Servent::readyRead()
 602{
 603    Q_D( Servent );
 604    Q_ASSERT( this->thread() == QThread::currentThread() );
 605    QPointer< QTcpSocketExtra > sock = (QTcpSocketExtra*)sender();
 606    tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Starting to read from new incoming connection from: " << sock->peerAddress().toString();
 607
 608    if ( sock.isNull() || sock.data()->_disowned )
 609    {
 610        return;
 611    }
 612
 613    if ( sock.data()->_msg.isNull() )
 614    {
 615        char msgheader[ Msg::headerSize() ];
 616        if ( sock.data()->bytesAvailable() < Msg::headerSize() )
 617            return;
 618
 619        sock.data()->read( (char*) &msgheader, Msg::headerSize() );
 620        sock.data()->_msg = Msg::begin( (char*) &msgheader );
 621    }
 622
 623    if ( sock.data()->bytesAvailable() < sock.data()->_msg->length() )
 624        return;
 625
 626    QByteArray ba = sock.data()->read( sock.data()->_msg->length() );
 627    sock.data()->_msg->fill( ba );
 628
 629    if ( !sock.data()->_msg->is( Msg::JSON ) )
 630    {
 631        tDebug() << ba;
 632        tDebug() << sock.data()->_msg->payload();
 633        Q_ASSERT( sock.data()->_msg->is( Msg::JSON ) );
 634    }
 635
 636    ControlConnection* cc = 0;
 637    bool ok;
 638    QString key, conntype, nodeid, controlid;
 639    QVariantMap m = TomahawkUtils::parseJson( sock.data()->_msg->payload(), &ok ).toMap();
 640    if ( !ok )
 641    {
 642        tDebug() << "Invalid JSON on new connection, aborting";
 643        goto closeconnection;
 644    }
 645
 646    conntype  = m.value( "conntype" ).toString();
 647    key       = m.value( "key" ).toString();
 648    nodeid    = m.value( "nodeid" ).toString();
 649    controlid = m.value( "controlid" ).toString();
 650
 651    tDebug( LOGVERBOSE ) << "Incoming connection details:" << m;
 652    if ( !nodeid.isEmpty() ) // only control connections send nodeid
 653    {
 654        QMutexLocker locker( &d->controlconnectionsMutex );
 655        bool dupe = false;
 656        if ( d_func()->connectedNodes.contains( nodeid ) )
 657        {
 658            tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Connected nodes contains it.";
 659            dupe = true;
 660        }
 661
 662        foreach ( ControlConnection* con, d_func()->controlconnections )
 663        {
 664            Q_ASSERT( con );
 665
 666            tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Known connection:" << con->id();
 667            // Only check for known inboud ControlConnections
 668            if ( con->id() == nodeid && !con->outbound() )
 669            {
 670                dupe = true;
 671                break;
 672            }
 673        }
 674
 675        // for zeroconf there might be no offer, that case is handled later
 676        ControlConnection* ccMatch = qobject_cast< ControlConnection* >( d_func()->offers.value( key ).data() );
 677        if ( dupe && ccMatch )
 678        {
 679            tLog() << "Duplicate control connection detected, dropping:" << nodeid << conntype;
 680
 681            tDebug() << "PEERINFO: to be dropped connection has following peers";
 682            foreach ( const peerinfo_ptr& currentPeerInfo, ccMatch->peerInfos() )
 683            {
 684                peerInfoDebug( currentPeerInfo );
 685            }
 686
 687            foreach ( ControlConnection* keepConnection, d_func()->controlconnections )
 688            {
 689                Q_ASSERT( keepConnection );
 690
 691                // Only check for known inboud ControlConnections
 692                if ( keepConnection->id() == nodeid && !keepConnection->outbound() )
 693                {
 694                    tDebug() << "Keep connection" << keepConnection->name() << "with following peers";
 695                    foreach ( const peerinfo_ptr& currentPeerInfo, keepConnection->peerInfos() )
 696                        peerInfoDebug( currentPeerInfo );
 697
 698                    tDebug() << "Add these peers now";
 699                    foreach ( const peerinfo_ptr& currentPeerInfo, ccMatch->peerInfos() )
 700                    {
 701                        tDebug() << "Adding" << currentPeerInfo->id();
 702                        keepConnection->addPeerInfo( currentPeerInfo );
 703                    }
 704                    tDebug() << "Done adding.";
 705                }
 706            }
 707            goto closeconnection;
 708        }
 709    }
 710
 711    {
 712        QMutexLocker locker( &d->controlconnectionsMutex );
 713        foreach ( ControlConnection* con, d_func()->controlconnections )
 714        {
 715            Q_ASSERT( con );
 716
 717            // Only check for known inboud ControlConnections
 718            if ( con->id() == controlid && !con->outbound() )
 719            {
 720                cc = con;
 721                break;
 722            }
 723        }
 724    }
 725
 726    // they connected to us and want something we are offering
 727    if ( conntype == "accept-offer" || conntype == "push-offer" )
 728    {
 729        sock.data()->_msg.clear();
 730        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << key << nodeid << "socket peer address =" << sock.data()->peerAddress() << "socket peer name =" << sock.data()->peerName();
 731        Connection* conn = claimOffer( cc, nodeid, key, sock.data()->peerAddress() );
 732        if ( !conn )
 733        {
 734            tLog() << "claimOffer FAILED, key:" << key << nodeid;
 735            goto closeconnection;
 736        }
 737        if ( sock.isNull() )
 738        {
 739            tLog() << "Socket has become null, possibly took too long to make an ACL decision, key:" << key << nodeid;
 740            return;
 741        }
 742        else if ( !sock.data()->isValid() )
 743        {
 744            tLog() << "Socket has become invalid, possibly took too long to make an ACL decision, key:" << key << nodeid;
 745            goto closeconnection;
 746        }
 747        tDebug( LOGVERBOSE ) << "claimOffer OK:" << key << nodeid;
 748
 749        if ( !nodeid.isEmpty() )
 750        {
 751            conn->setId( nodeid );
 752            registerControlConnection( qobject_cast<ControlConnection*>(conn) );
 753        }
 754
 755        handoverSocket( conn, sock.data() );
 756        return;
 757    }
 758    else
 759    {
 760        tLog() << "Invalid or unhandled conntype";
 761    }
 762
 763    // fallthru to cleanup:
 764closeconnection:
 765    tLog() << "Closing incoming connection, something was wrong.";
 766    sock.data()->_msg.clear();
 767    sock.data()->disconnectFromHost();
 768}
 769
 770
 771// creates a new tcp connection to peer from conn, handled by given connector
 772// new_conn is responsible for sending the first msg, if needed
 773void
 774Servent::createParallelConnection( Connection* orig_conn, Connection* new_conn, const QString& key )
 775{
 776    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << ", key:" << key << thread() << orig_conn;
 777    // if we can connect to them directly:
 778    if ( orig_conn && orig_conn->outbound() )
 779    {
 780        SipInfo info = SipInfo();
 781        info.setVisible( true );
 782        info.setKey( key );
 783        info.setNodeId( orig_conn->id() );
 784        info.setHost( orig_conn->socket()->peerName() );
 785        info.setPort( orig_conn->peerPort() );
 786        Q_ASSERT( info.isValid() );
 787        initiateConnection( info, new_conn );
 788    }
 789    else // ask them to connect to us:
 790    {
 791        QString tmpkey = uuid();
 792        tLog() << "Asking them to connect to us using" << tmpkey ;
 793        registerOffer( tmpkey, new_conn );
 794
 795        QVariantMap m;
 796        m.insert( "conntype", "request-offer" );
 797        m.insert( "key", tmpkey );
 798        m.insert( "offer", key );
 799        m.insert( "controlid", Database::instance()->impl()->dbid() );
 800
 801        if (orig_conn) {
 802            orig_conn->sendMsg( Msg::factory( TomahawkUtils::toJson( m ), Msg::JSON ) );
 803        }
 804    }
 805}
 806
 807
 808void
 809Servent::socketConnected()
 810{
 811    QTcpSocketExtra* sock = (QTcpSocketExtra*)sender();
 812
 813    tLog( LOGVERBOSE ) << Q_FUNC_INFO << thread() << "socket:" << sock << ", hostaddr:" << sock->peerAddress() << ", hostname:" << sock->peerName();
 814
 815    if ( sock->_conn.isNull() )
 816    {
 817        sock->close();
 818        sock->deleteLater();
 819        tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Socket's connection was null, could have timed out or been given an invalid address";
 820        return;
 821    }
 822
 823    Connection* conn = sock->_conn.data();
 824    handoverSocket( conn, sock );
 825}
 826
 827
 828// transfers ownership of socket to the connection and inits the connection
 829void
 830Servent::handoverSocket( Connection* conn, QTcpSocketExtra* sock )
 831{
 832    Q_ASSERT( conn );
 833    Q_ASSERT( sock );
 834    Q_ASSERT( conn->socket().isNull() );
 835    Q_ASSERT( sock->isValid() );
 836
 837    disconnect( sock, SIGNAL( readyRead() ),    this, SLOT( readyRead() ) );
 838    disconnect( sock, SIGNAL( disconnected() ), sock, SLOT( deleteLater() ) );
 839    disconnect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) );
 840
 841    sock->_disowned = true;
 842    conn->setOutbound( sock->_outbound );
 843    conn->setPeerPort( sock->peerPort() );
 844
 845    conn->start( sock );
 846}
 847
 848
 849void
 850Servent::cleanupSocket( QTcpSocketExtra* sock )
 851{
 852    if ( !sock )
 853    {
 854        tLog() << "SocketError, sock is null";
 855        return;
 856    }
 857
 858    if ( sock->_conn.isNull() )
 859    {
 860        tLog() << "SocketError, connection is null";
 861    }
 862    sock->deleteLater();
 863}
 864
 865
 866void
 867Servent::initiateConnection( const SipInfo& sipInfo, Connection* conn )
 868{
 869    Q_D( Servent );
 870
 871    Q_ASSERT( sipInfo.isValid() );
 872    Q_ASSERT( sipInfo.isVisible() );
 873    Q_ASSERT( sipInfo.port() > 0 );
 874    Q_ASSERT( conn );
 875
 876    // Check that we are not connecting to ourselves
 877    QList<QHostAddress> addresses = d->externalAddresses;
 878
 879    if ( d->externalListenAll )
 880    {
 881        addresses = QNetworkInterface::allAddresses();
 882    }
 883
 884    foreach ( QHostAddress ha, addresses )
 885    {
 886        if ( sipInfo.host() == ha.toString() && sipInfo.port() == d_func()->port )
 887        {
 888            tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << sipInfo.host() << ":" << sipInfo.port() << ": same IP as ourselves.";
 889            return;
 890        }
 891    }
 892    if ( sipInfo.host() == d_func()->externalHostname && sipInfo.port() == d_func()->port )
 893    {
 894        tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Tomahawk won't try to connect to" << sipInfo.host() << ":" << sipInfo.port() << ": same IP as ourselves.";
 895        return;
 896    }
 897
 898    if ( conn->firstMessage().isNull() )
 899    {
 900        QVariantMap m;
 901        m["conntype"]  = "accept-offer";
 902        m["key"]       = sipInfo.key();
 903        m["controlid"] = Database::instance()->impl()->dbid();
 904        conn->setFirstMessage( m );
 905    }
 906
 907    QTcpSocketExtra* sock = new QTcpSocketExtra();
 908    sock->setConnectTimeout( CONNECT_TIMEOUT );
 909    sock->_disowned = false;
 910    sock->_conn = conn;
 911    sock->_outbound = true;
 912
 913    connect( sock, SIGNAL( connected() ), SLOT( socketConnected() ) );
 914    connect( sock, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) );
 915
 916    if ( !conn->peerIpAddress().isNull() )
 917        sock->connectToHost( conn->peerIpAddress(), sipInfo.port(), QTcpSocket::ReadWrite );
 918    else
 919        sock->connectToHost( sipInfo.host(), sipInfo.port(), QTcpSocket::ReadWrite );
 920    sock->moveToThread( thread() );
 921}
 922
 923
 924void
 925Servent::socketError( QAbstractSocket::SocketError e )
 926{
 927    QTcpSocketExtra* sock = (QTcpSocketExtra*)sender();
 928    if ( !sock )
 929    {
 930        tLog() << "SocketError, sock is null";
 931        return;
 932    }
 933
 934    if ( !sock->_conn.isNull() )
 935    {
 936        Connection* conn = sock->_conn.data();
 937        tLog() << "Servent::SocketError:" << e << conn->id() << conn->name();
 938
 939        if ( !sock->_disowned )
 940        {
 941            // connection will delete if we already transferred ownership, otherwise:
 942            sock->deleteLater();
 943        }
 944
 945        conn->markAsFailed(); // will emit failed, then finished
 946    }
 947    else
 948    {
 949        tLog() << "SocketError, connection is null";
 950        sock->deleteLater();
 951    }
 952}
 953
 954
 955void
 956Servent::checkACLResult( const QString& nodeid, const QString& username, Tomahawk::ACLStatus::Type peerStatus )
 957{
 958    Q_D( Servent );
 959
 960    if ( !d->queuedForACLResult.contains( username ) )
 961    {
 962        return;
 963    }
 964    if ( !d->queuedForACLResult.value( username ).contains( nodeid ) )
 965    {
 966        return;
 967    }
 968
 969    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << QString( "ACL status for user %1 is" ).arg( username ) << peerStatus;
 970    QSet<Tomahawk::peerinfo_ptr> peerInfos = d->queuedForACLResult.value( username ).value( nodeid );
 971    if ( peerStatus == Tomahawk::ACLStatus::Stream )
 972    {
 973        foreach ( const Tomahawk::peerinfo_ptr& peerInfo, peerInfos )
 974        {
 975            registerPeer( peerInfo );
 976        }
 977
 978    }
 979    // We have a result, so remove from queue
 980    d->queuedForACLResult[username].remove( nodeid );
 981}
 982
 983
 984void
 985Servent::ipDetected()
 986{
 987    Q_D( Servent );
 988    QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
 989
 990    if ( reply->error() == QNetworkReply::NoError )
 991    {
 992        bool ok;
 993        // We are called when the NetworkReply has finished so we should have all data available.
 994        const QVariantMap res = TomahawkUtils::parseJson( reply->readAll(), &ok ).toMap();
 995        if ( !ok )
 996        {
 997            tLog() << Q_FUNC_INFO << "Failed parsing ip-autodetection response";
 998            d->externalPort = -1;
 999            emit ipDetectionFailed( QNetworkReply::NoError, tr( "Automatically detecting external IP failed: Could not parse JSON response." ) );
1000        }
1001        else
1002        {
1003            QString externalIP = res.value( "ip" ).toString();
1004            tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Found external IP:" << externalIP;
1005            d->externalHostname = externalIP;
1006        }
1007    }
1008    else
1009    {
1010        d->externalPort = -1;
1011        tLog() << Q_FUNC_INFO << "ip-autodetection returned an error:" << reply->errorString();
1012        emit ipDetectionFailed( reply->error(), tr( "Automatically detecting external IP failed: %1" ).arg( reply->errorString() ) );
1013    }
1014
1015    d->ready = true;
1016    emit ready();
1017}
1018
1019
1020void
1021Servent::reverseOfferRequest( ControlConnection* orig_conn, const QString& theirdbid, const QString& key, const QString& theirkey )
1022{
1023    Q_ASSERT( this->thread() == QThread::currentThread() );
1024
1025    tDebug( LOGVERBOSE ) << "Servent::reverseOfferRequest received for" << key;
1026    Connection* new_conn = claimOffer( orig_conn, theirdbid, key );
1027    if ( !new_conn )
1028    {
1029        tDebug() << "claimOffer failed, killing requesting connection out of spite";
1030        orig_conn->shutdown();
1031        return;
1032    }
1033
1034    QVariantMap m;
1035    m["conntype"]  = "push-offer";
1036    m["key"]       = theirkey;
1037    m["controlid"] = Database::instance()->impl()->dbid();
1038    new_conn->setFirstMessage( m );
1039    createParallelConnection( orig_conn, new_conn, theirkey );
1040}
1041
1042
1043bool
1044Servent::visibleExternally() const
1045{
1046    return !d_func()->externalHostname.isNull() || !d_func()->externalAddresses.isEmpty();
1047}
1048
1049
1050bool
1051Servent::ipv6ConnectivityLikely() const
1052{
1053    foreach ( QHostAddress ha, d_func()->externalAddresses )
1054    {
1055        if ( ha.protocol() == QAbstractSocket::IPv6Protocol && Servent::isValidExternalIP( ha ) )
1056        {
1057            return true;
1058        }
1059    }
1060
1061    return false;
1062}
1063
1064
1065int
1066Servent::port() const
1067{
1068    return d_func()->port;
1069}
1070
1071
1072QList<QHostAddress>
1073Servent::addresses() const
1074{
1075    Q_D( const Servent );
1076
1077    if ( d->externalListenAll )
1078    {
1079        QList<QHostAddress> addresses( QNetworkInterface::allAddresses() );
1080        cleanAddresses( addresses );
1081        return addresses;
1082    }
1083
1084    return d->externalAddresses;
1085}
1086
1087
1088QString
1089Servent::additionalAddress() const
1090{
1091    return d_func()->externalHostname;
1092}
1093
1094
1095int
1096Servent::additionalPort() const
1097{
1098    return d_func()->externalPort;
1099}
1100
1101
1102bool
1103equalByIPv6Address( QHostAddress a1, QHostAddress a2 )
1104{
1105    Q_IPV6ADDR addr1 = a1.toIPv6Address();
1106    Q_IPV6ADDR addr2 = a2.toIPv6Address();
1107
1108    for (int i = 0; i < 16; ++i)
1109    {
1110        if ( addr1[i] != addr2[i] )
1111            return false;
1112    }
1113    return true;
1114}
1115
1116
1117// return the appropriate connection for a given offer key, or NULL if invalid
1118Connection*
1119Servent::claimOffer( ControlConnection* cc, const QString &nodeid, const QString &key, const QHostAddress peer )
1120{
1121    Q_D( Servent );
1122
1123    // magic key for stream connections:
1124    if ( key.startsWith( "FILE_REQUEST_KEY:" ) )
1125    {
1126        // check if the source IP matches an existing, authenticated connection
1127        if ( !d->noAuth && peer != QHostAddress::Any && !isIPWhitelisted( peer ) )
1128        {
1129            bool authed = false;
1130            tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Checking for ControlConnection with IP" << peer;
1131            QMutexLocker locker( &d->controlconnectionsMutex );
1132            foreach ( ControlConnection* cc, d->controlconnections )
1133            {
1134                if ( cc->socket() )
1135                    tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Probing:" << cc->name() << cc->socket()->peerAddress();
1136                else
1137                    tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Probing error:" << cc->name() << "has invalid socket";
1138
1139                // Always compare IPv6 addresses as IPv4 address are sometime simply IPv4 addresses, sometimes mapped IPv6 addresses
1140                if ( cc->socket() && equalByIPv6Address( cc->socket()->peerAddress(), peer ) )
1141                {
1142                    authed = true;
1143                    break;
1144                }
1145            }
1146            if ( !authed )
1147            {
1148                tLog() << "File transfer request rejected as the request came from an IP which we could not match to existing peer connections.";
1149                return NULL;
1150            }
1151        }
1152
1153        QString fid = key.right( key.length() - 17 );
1154        StreamConnection* sc = new StreamConnection( this, cc, fid );
1155        return sc;
1156    }
1157
1158    if ( key == "whitelist" ) // LAN IP address, check source IP
1159    {
1160        if ( isIPWhitelisted( peer ) )
1161        {
1162            tDebug() << "Connection is from whitelisted IP range (LAN)";
1163            ControlConnection* conn = new ControlConnection( this );
1164            conn->setName( peer.toString() );
1165
1166            Tomahawk::Accounts::Account* account = Tomahawk::Accounts::AccountManager::instance()->zeroconfAccount();
1167
1168            // if we get this connection the account should exist and be enabled
1169            Q_ASSERT( account );
1170            Q_ASSERT( account->enabled() );
1171
1172            // this is terrible, actually there should be a way to let this be created by the zeroconf plugin
1173            // because this way we rely on the ip being used as id in two totally different parts of the code
1174            Tomahawk::peerinfo_ptr peerInfo = Tomahawk::PeerInfo::get( account->sipPlugin(), peer.toString(), Tomahawk::PeerInfo::AutoCreate );
1175            peerInfo->setContactId( peer.toString() );
1176            peerInfoDebug( peerInfo );
1177            conn->addPeerInfo( peerInfo );
1178            return conn;
1179        }
1180        else
1181        {
1182            tDebug() << "Connection claimed to be whitelisted, but wasn't.";
1183            return NULL;
1184        }
1185    }
1186
1187    if ( d->lazyoffers.contains( key ) )
1188    {
1189        ControlConnection* conn = new ControlConnection( this );
1190        conn->setName( d->lazyoffers.value( key ).first->contactId() );
1191        conn->addPeerInfo( d->lazyoffers.value( key ).first );
1192        conn->setId( d->lazyoffers.value( key ).second );
1193
1194        if ( !nodeid.isEmpty() )
1195        {
1196            // Used by the connection for the ACL check
1197            // If there isn't a nodeid it's not the first connection and will already have been stopped
1198            conn->setNodeId( nodeid );
1199        }
1200
1201        return conn;
1202    }
1203    else if ( d->offers.contains( key ) )
1204    {
1205        QPointer<Connection> conn = d->offers.value( key );
1206        if ( conn.isNull() )
1207        {
1208            // This can happen if it's a streamconnection, but the audioengine has
1209            // already closed the iodevice, causing the connection to be deleted before
1210            // the peer connects and provides the first byte
1211            tLog() << Q_FUNC_INFO << "invalid/expired offer:" << key;
1212            return NULL;
1213        }
1214
1215        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "nodeid is: " << nodeid;
1216        if ( !nodeid.isEmpty() )
1217        {
1218            // Used by the connection for the ACL check
1219            // If there isn't a nodeid it's not the first connection and will already have been stopped
1220            conn->setNodeId( nodeid );
1221        }
1222
1223        if ( conn.data()->onceOnly() )
1224        {
1225            d->offers.remove( key );
1226            return conn.data();
1227        }
1228        else
1229        {
1230            return conn.data()->clone();
1231        }
1232    }
1233    else if ( d->noAuth )
1234    {
1235        Connection* conn;
1236        conn = new ControlConnection( this );
1237        conn->setName( key );
1238        return conn;
1239    }
1240    else
1241    {
1242        tLog() << "Invalid offer key:" << key;
1243        return NULL;
1244    }
1245}
1246
1247
1248void
1249Servent::remoteIODeviceFactory( const Tomahawk::result_ptr& result, const QString& url,
1250                                std::function< void ( const QString&, QSharedPointer< QIODevice >& ) > callback )
1251{
1252    QSharedPointer<QIODevice> sp;
1253
1254    QStringList parts = url.mid( QString( "servent://" ).length() ).split( "\t" );
1255    const QString sourceName = parts.at( 0 );
1256    const QString fileId = parts.at( 1 );
1257    source_ptr s = SourceList::instance()->get( sourceName );
1258    if ( s.isNull() || !s->controlConnection() )
1259    {
1260        callback( result->url(), sp );
1261        return;
1262    }
1263
1264    ControlConnection* cc = s->controlConnection();
1265    StreamConnection* sc = new StreamConnection( this, cc, fileId, result );
1266    createParallelConnection( cc, sc, QString( "FILE_REQUEST_KEY:%1" ).arg( fileId ) );
1267
1268    // std::functions cannot accept temporaries as parameters
1269    sp = sc->iodevice();
1270    callback( result->url(), sp );
1271}
1272
1273
1274void
1275Servent::registerStreamConnection( StreamConnection* sc )
1276{
1277    Q_ASSERT( !d_func()->scsessions.contains( sc ) );
1278    tDebug( LOGVERBOSE ) << "Registering Stream" << d_func()->scsessions.length() + 1;
1279
1280    QMutexLocker lock( &d_func()->ftsession_mut );
1281    d_func()->scsessions.append( sc );
1282
1283    printCurrentTransfers();
1284    emit streamStarted( sc );
1285}
1286
1287
1288void
1289Servent::onStreamFinished( StreamConnection* sc )
1290{
1291    Q_ASSERT( sc );
1292    tDebug( LOGVERBOSE ) << "Stream Finished, unregistering" << sc->id();
1293
1294    QMutexLocker lock( &d_func()->ftsession_mut );
1295    d_func()->scsessions.removeAll( sc );
1296
1297    printCurrentTransfers();
1298    emit streamFinished( sc );
1299}
1300
1301
1302// used for debug output:
1303void
1304Servent::printCurrentTransfers()
1305{
1306    int k = 1;
1307//    qDebug() << "~~~ Active file transfer connections:" << m_scsessions.length();
1308    foreach ( StreamConnection* i, d_func()->scsessions )
1309    {
1310        qDebug() << k << ") " << i->id();
1311    }
1312    qDebug() << endl;
1313}
1314
1315
1316void
1317Servent::cleanAddresses( QList<QHostAddress>& addresses ) const
1318{
1319    QList<QHostAddress>::iterator iter = addresses.begin();
1320    while ( iter != addresses.end() )
1321    {
1322        QString hostString = iter->toString();
1323        if ( hostString.startsWith( QLatin1String( "127.0.0." ) ) //< IPv4 localhost
1324             // IPv6 localhost
1325             || hostString == "::1"
1326             // IPv4 localhost as IPv6 address
1327             || hostString == "::7F00:1" )
1328        {
1329            iter = addresses.erase( iter );
1330            // Always continue if we changed iter as we might have reached the end
1331            continue;
1332        }
1333
1334        // Remove IPv6 link local addresses
1335        if ( iter->isInSubnet( QHostAddress::parseSubnet( "fe80::/10" ) ) )
1336        {
1337            iter = addresses.erase( iter );
1338            continue;
1339        }
1340
1341        // Advance to next element
1342        ++iter;
1343    }
1344}
1345
1346
1347bool
1348Servent::isIPWhitelisted( QHostAddress ip )
1349{
1350    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Performing checks against ip" << ip.toString();
1351    typedef QPair< QHostAddress, int > range;
1352    QList< range > subnetEntries;
1353
1354    QList< QNetworkInterface > networkInterfaces = QNetworkInterface::allInterfaces();
1355    foreach ( QNetworkInterface interface, networkInterfaces )
1356    {
1357        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Checking interface" << interface.humanReadableName();
1358        QList< QNetworkAddressEntry > addressEntries = interface.addressEntries();
1359        foreach ( QNetworkAddressEntry addressEntry, addressEntries )
1360        {
1361            tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Checking address entry with ip" << addressEntry.ip().toString() << "and prefix length" << addressEntry.prefixLength();
1362            if ( ip.isInSubnet( addressEntry.ip(), addressEntry.prefixLength() ) )
1363            {
1364                tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "success";
1365                return true;
1366            }
1367        }
1368    }
1369
1370    // Qt cannot cope correctly with IPv4 addresses mapped into the IPv6
1371    // address space
1372    if ( ip.protocol() == QAbstractSocket::IPv6Protocol )
1373    {
1374        Q_IPV6ADDR ipv6 = ip.toIPv6Address();
1375        // Check if it is actually an IPv4 address
1376        // First 80 bits are zero, then 16 bits 1s
1377        bool isIPv4 = true;
1378        for ( int i = 0; i < 9; i++) {
1379            isIPv4 &= ( ipv6[i] == 0 );
1380        }
1381        isIPv4 &= ( ipv6[10] == 0xff );
1382        isIPv4 &= ( ipv6[11] == 0xff );
1383
1384        if ( isIPv4 )
1385        {
1386            // Convert to a real IPv4 address and rerun checks
1387            quint32 ipv4 = (static_cast<quint32>(ipv6[12]) << 24)
1388                    | (static_cast<quint32>(ipv6[13]) << 16)
1389                    | (static_cast<quint32>(ipv6[14]) << 8)
1390                    | static_cast<quint32>(ipv6[15]);
1391            QHostAddress addr( ipv4 );
1392            return isIPWhitelisted( addr );
1393        }
1394    }
1395
1396    tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "failure";
1397    return false;
1398}
1399
1400
1401bool
1402Servent::connectedToSession( const QString& session )
1403{
1404    Q_D( Servent );
1405
1406    QMutexLocker locker( &d->controlconnectionsMutex );
1407    foreach ( ControlConnection* cc, d_func()->controlconnections )
1408    {
1409        Q_ASSERT( cc );
1410
1411        if ( cc->id() == session )
1412            return true;
1413    }
1414
1415    return false;
1416}
1417
1418
1419unsigned int
1420Servent::numConnectedPeers() const
1421{
1422    return d_func()->controlconnections.length();
1423}
1424
1425
1426QList<StreamConnection*>
1427Servent::streams() const
1428{
1429    return d_func()->scsessions;
1430}
1431
1432
1433void
1434Servent::triggerDBSync()
1435{
1436    // tell peers we have new stuff they should sync
1437    QList< source_ptr > sources = SourceList::instance()->sources();
1438    foreach ( const source_ptr& src, sources )
1439    {
1440        // skip local source
1441        if ( src.isNull() || src->isLocal() )
1442            continue;
1443
1444        if ( src->controlConnection() && src->controlConnection()->dbSyncConnection() ) // source online?
1445            src->controlConnection()->dbSyncConnection()->trigger();
1446    }
1447    emit dbSyncTriggered();
1448}
1449
1450
1451bool
1452Servent::isReady() const
1453{
1454    return d_func()->ready;
1455}