You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ktorrent/libktorrent/kademlia/kbucket.cpp

356 lines
8.9 KiB

/***************************************************************************
* Copyright (C) 2005 by Joris Guisson *
* joris.guisson@gmail.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include <ksocketaddress.h>
#include <util/file.h>
#include <util/log.h>
#include <util/functions.h>
#include <netinet/in.h>
#include "kbucket.h"
#include "kclosestnodessearch.h"
#include "rpcserver.h"
#include "node.h"
using namespace KNetwork;
using namespace bt;
namespace dht
{
KBucketEntry::KBucketEntry()
{
last_responded = bt::GetCurrentTime();
failed_queries = 0;
questionable_pings = 0;
}
KBucketEntry::KBucketEntry(const KInetSocketAddress & addr,const Key & id)
: addr(addr),node_id(id)
{
last_responded = bt::GetCurrentTime();
failed_queries = 0;
questionable_pings = 0;
}
KBucketEntry::KBucketEntry(const KBucketEntry & other)
: addr(other.addr),node_id(other.node_id),
last_responded(other.last_responded),failed_queries(other.failed_queries),questionable_pings(other.questionable_pings)
{}
KBucketEntry::~KBucketEntry()
{}
KBucketEntry & KBucketEntry::operator = (const KBucketEntry & other)
{
addr = other.addr;
node_id = other.node_id;
last_responded = other.last_responded;
failed_queries = other.failed_queries;
questionable_pings = other.questionable_pings;
return *this;
}
bool KBucketEntry::operator == (const KBucketEntry & entry) const
{
return addr == entry.addr && node_id == entry.node_id;
}
bool KBucketEntry::isGood() const
{
if (bt::GetCurrentTime() - last_responded > 15 * 60 * 1000)
return false;
else
return true;
}
bool KBucketEntry::isQuestionable() const
{
if (bt::GetCurrentTime() - last_responded > 15 * 60 * 1000)
return true;
else
return false;
}
bool KBucketEntry::isBad() const
{
if (isGood())
return false;
return failed_queries > 2 || questionable_pings > 2;
}
void KBucketEntry::hasResponded()
{
last_responded = bt::GetCurrentTime();
failed_queries = 0; // reset failed queries
questionable_pings = 0;
}
//////////////////////////////////////////////////////////
KBucket::KBucket(Uint32 idx,RPCServer* srv,Node* node)
: idx(idx),srv(srv),node(node)
{
last_modified = bt::GetCurrentTime();
refresh_task = 0;
}
KBucket::~KBucket()
{}
void KBucket::insert(const KBucketEntry & entry)
{
TQValueList<KBucketEntry>::iterator i = entries.find(entry);
// If in the list, move it to the end
if (i != entries.end())
{
KBucketEntry & e = *i;
e.hasResponded();
last_modified = bt::GetCurrentTime();
entries.remove(i);
entries.append(entry);
return;
}
// insert if not already in the list and we still have room
if (i == entries.end() && entries.count() < dht::K)
{
entries.append(entry);
last_modified = bt::GetCurrentTime();
}
else if (!replaceBadEntry(entry))
{
// ping questionable nodes when replacing a bad one fails
pingQuestionable(entry);
}
}
void KBucket::onResponse(RPCCall* c,MsgBase* rsp)
{
last_modified = bt::GetCurrentTime();
if (!pending_entries_busy_pinging.contains(c))
return;
KBucketEntry entry = pending_entries_busy_pinging[c];
pending_entries_busy_pinging.erase(c); // call is done so erase it
// we have a response so try to find the next bad or questionable node
// if we do not have room see if we can get rid of some bad peers
if (!replaceBadEntry(entry)) // if no bad peers ping a questionable one
pingQuestionable(entry);
}
void KBucket::onTimeout(RPCCall* c)
{
if (!pending_entries_busy_pinging.contains(c))
return;
KBucketEntry entry = pending_entries_busy_pinging[c];
// replace the entry which timed out
TQValueList<KBucketEntry>::iterator i;
for (i = entries.begin();i != entries.end();i++)
{
KBucketEntry & e = *i;
if (e.getAddress() == c->getRequest()->getOrigin())
{
last_modified = bt::GetCurrentTime();
entries.remove(i);
entries.append(entry);
break;
}
}
pending_entries_busy_pinging.erase(c); // call is done so erase it
// see if we can do another pending entry
if (pending_entries_busy_pinging.count() < 2 && pending_entries.count() > 0)
{
KBucketEntry pe = pending_entries.front();
pending_entries.pop_front();
if (!replaceBadEntry(pe)) // if no bad peers ping a questionable one
pingQuestionable(pe);
}
}
void KBucket::pingQuestionable(const KBucketEntry & replacement_entry)
{
if (pending_entries_busy_pinging.count() >= 2)
{
pending_entries.append(replacement_entry); // lets not have to many pending_entries calls going on
return;
}
TQValueList<KBucketEntry>::iterator i;
// we haven't found any bad ones so try the questionable ones
for (i = entries.begin();i != entries.end();i++)
{
KBucketEntry & e = *i;
if (e.isQuestionable())
{
Out(SYS_DHT|LOG_DEBUG) << "Pinging questionable node : " << e.getAddress().toString() << endl;
PingReq* p = new PingReq(node->getOurID());
p->setDestination(e.getAddress());
RPCCall* c = srv->doCall(p);
if (c)
{
e.onPingQuestionable();
c->addListener(this);
// add the pending entry
pending_entries_busy_pinging.insert(c,replacement_entry);
return;
}
}
}
}
bool KBucket::replaceBadEntry(const KBucketEntry & entry)
{
TQValueList<KBucketEntry>::iterator i;
for (i = entries.begin();i != entries.end();i++)
{
KBucketEntry & e = *i;
if (e.isBad())
{
// bad one get rid of it
last_modified = bt::GetCurrentTime();
entries.remove(i);
entries.append(entry);
return true;
}
}
return false;
}
bool KBucket::contains(const KBucketEntry & entry) const
{
return entries.contains(entry);
}
void KBucket::findKClosestNodes(KClosestNodesSearch & kns)
{
TQValueList<KBucketEntry>::iterator i = entries.begin();
while (i != entries.end())
{
kns.tryInsert(*i);
i++;
}
}
bool KBucket::onTimeout(const KInetSocketAddress & addr)
{
TQValueList<KBucketEntry>::iterator i;
for (i = entries.begin();i != entries.end();i++)
{
KBucketEntry & e = *i;
if (e.getAddress() == addr)
{
e.requestTimeout();
return true;
}
}
return false;
}
bool KBucket::needsToBeRefreshed() const
{
bt::TimeStamp now = bt::GetCurrentTime();
if (last_modified > now)
{
last_modified = now;
return false;
}
return !refresh_task && entries.count() > 0 && (now - last_modified > BUCKET_REFRESH_INTERVAL);
}
void KBucket::updateRefreshTimer()
{
last_modified = bt::GetCurrentTime();
}
void KBucket::save(bt::File & fptr)
{
BucketHeader hdr;
hdr.magic = BUCKET_MAGIC_NUMBER;
hdr.index = idx;
hdr.num_entries = entries.count();
fptr.write(&hdr,sizeof(BucketHeader));
TQValueList<KBucketEntry>::iterator i;
for (i = entries.begin();i != entries.end();i++)
{
KBucketEntry & e = *i;
const KIpAddress & ip = e.getAddress().ipAddress();
Uint8 tmp[26];
bt::WriteUint32(tmp,0,ip.IPv4Addr());
bt::WriteUint16(tmp,4,e.getAddress().port());
memcpy(tmp+6,e.getID().getData(),20);
fptr.write(tmp,26);
}
}
void KBucket::load(bt::File & fptr,const BucketHeader & hdr)
{
if (hdr.num_entries > K)
return;
for (Uint32 i = 0;i < hdr.num_entries;i++)
{
Uint8 tmp[26];
if (fptr.read(tmp,26) != 26)
return;
entries.append(KBucketEntry(
KInetSocketAddress(
KIpAddress(bt::ReadUint32(tmp,0)),
bt::ReadUint16(tmp,4)),
dht::Key(tmp+6)));
}
}
void KBucket::onFinished(Task* t)
{
if (t == refresh_task)
refresh_task = 0;
}
void KBucket::setRefreshTask(Task* t)
{
refresh_task = t;
if (refresh_task)
{
connect(refresh_task,TQT_SIGNAL(finished( Task* )),
this,TQT_SLOT(onFinished( Task* )));
}
}
}
#include "kbucket.moc"