/* This file is part of GNUnet. (C) 2001, 2002, 2003, 2004 Christian Grothoff (and other contributing authors) GNUnet 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, or (at your option) any later version. GNUnet 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 GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file applications/afs/module/querymanager.c * @brief forwarding of queries * @author Christian Grothoff * * The query manager is responsible for queueing queries. Queued * queries are used to fill buffers (instead of using noise). The QM * is also responsible for selecting the initial set of nodes that * will receive the query. For a good choice, it keeps track of which * nodes were recently hot in answering queries. Some randomness is * preserved to ensure that we potentially find a better path.
*
* Routing is an incredibly hard problem, so please consider
* consulting with other gnunet-developers before making any
* significant changes here, even if you have CVS write access.
*/
#include "querymanager.h"
#define DEBUG_QUERYMANAGER NO
/**
* Set to 'YES' to play with 0.6.2b (and earlier) behavior.
*/
#define TRADITONAL_SELECTION NO
/* default size of the bitmap: 16 byte = 128 bit */
#define BITMAP_SIZE 16
/* of how many outbound queries do we simultaneously
keep track? */
#define QUERY_RECORD_COUNT 512
/**
* How much is a query worth 'in general' (even
* if there is no trust relationship between
* the peers!). Multiplied by the number of queries
* in the request. 20 is for '20 bytes / hash',
* so this is kind of the base unit.
*/
#define BASE_QUERY_PRIORITY 20
/**
* In this struct, we store information about a
* query that is being send from the local node to
* optimize the sending strategy.
*/
typedef struct {
/**
* How often did we send this query so far?
*/
unsigned int sendCount;
/**
* The message that we are sending.
*/
AFS_p2p_QUERY * msg;
/**
* Bit-map marking the hostIndices (computeIndex)
* of nodes that have received this query already.
* Note that the bit-map has a maximum size, if
* the index is out-of-bounds, it is hashed into
* the smaller size of the bitmap. There may thus be
* nodes with identical indices, in that case, only one of
* the nodes will receive the query.
*/
unsigned char bitmap[BITMAP_SIZE];
/**
* When do we stop forwarding (!) this query?
*/
cron_t expires;
/**
* How many nodes were connected when we
* initated sending this query?
*/
unsigned int activeConnections;
/**
* What is the total distance of the query to
* the connected nodes?
*/
unsigned long long totalDistance;
/**
* To how many peers has / will this query be transmitted?
*/
unsigned int transmissionCount;
/**
* To which peer will we never send this message?
*/
HostIdentity noTarget;
/**
* Sender identity, for a local client.
*/
ClientHandle localClient;
/**
* How important would it be to send the message
* to all peers in this bucket?
*/
int * rankings;
} QueryRecord;
/**
* Array of the queries we are currently sending out.
*/
static QueryRecord queries[QUERY_RECORD_COUNT];
/**
* Mutex for all query manager structures.
*/
static Mutex * queryManagerLock;
/**
* How many queries are in the given query (header given).
*/
#define NUMBER_OF_QUERIES(qhdr) (((ntohs(qhdr.size)-sizeof(AFS_p2p_QUERY))/sizeof(HashCode160)))
/**
* Linked list of peer ids with number of replies received.
*/
typedef struct RL_ {
HostIdentity responder;
unsigned int responseCount;
struct RL_ * next;
} ResponseList;
/**
* Structure for tracking from which peer we got valueable replies for
* which clients / other peers.
*/
typedef struct RTD_ {
/**
* For which client does this entry track replies?
* Only valid if localQueryOrigin == NULL!
*/
HostIdentity queryOrigin;
/**
* For which client does this entry track replies?
*/
ClientHandle localQueryOrigin;
/**
* Time at which we received the last reply
* for this client. Used to discard old entries
* eventually.
*/
TIME_T lastReplyReceived;
/**
* Linked list of peers that responded, with
* number of responses.
*/
ResponseList * responseList;
/**
* Linked list.
*/
struct RTD_ * next;
} ReplyTrackData;
/**
* Linked list tracking reply statistics. Synchronize access using
* the queryManagerLock!
*/
static ReplyTrackData * rtdList = NULL;
/**
* Cron job that ages the RTD data and that frees
* memory for entries that reach 0.
*/
static void ageRTD(void * unused) {
ReplyTrackData * pos;
ReplyTrackData * prev;
ResponseList * rpos;
ResponseList * rprev;
MUTEX_LOCK(queryManagerLock);
prev = NULL;
pos = rtdList;
while (pos != NULL) {
/* after 10 minutes, always discard everything */
if (pos->lastReplyReceived < TIME(NULL) - 600) {
while (pos->responseList != NULL) {
rpos = pos->responseList;
pos->responseList = rpos->next;
FREE(rpos);
}
}
/* otherwise, age reply counts */
rprev = NULL;
rpos = pos->responseList;
while (rpos != NULL) {
rpos->responseCount = rpos->responseCount / 2;
if (rpos->responseCount == 0) {
if (rprev == NULL)
pos->responseList = rpos->next;
else
rprev->next = rpos->next;
FREE(rpos);
if (rprev == NULL)
rpos = pos->responseList;
else
rpos = rprev->next;
continue;
}
}
/* if we have no counts for a peer anymore,
free pos entry */
if (pos->responseList == NULL) {
if (prev == NULL)
rtdList = pos->next;
else
prev->next = pos->next;
FREE(pos);
if (prev == NULL)
pos = rtdList;
else
pos = prev->next;
continue;
}
prev = pos;
pos = pos->next;
}
MUTEX_UNLOCK(queryManagerLock);
}
/**
* We received a reply from 'responder' to a query received from
* 'origin' (or 'localOrigin'). Update reply track data!
*
* @param origin only valid if localOrigin == NULL
* @param localOrigin origin if query was initiated by local client
* @param responder peer that send the reply
*/
void updateResponseData(const HostIdentity * origin,
ClientHandle localOrigin,
const HostIdentity * responder) {
ReplyTrackData * pos;
ReplyTrackData * prev;
ResponseList * rpos;
ResponseList * rprev;
if (responder == NULL)
return; /* we don't track local responses */
MUTEX_LOCK(queryManagerLock);
pos = rtdList;
prev = NULL;
while (pos != NULL) {
if ( (pos->localQueryOrigin == localOrigin) &&
( (localOrigin != NULL) ||
(0 == memcmp(origin,
&pos->queryOrigin,
sizeof(HostIdentity))) ) )
break; /* found */
prev = pos;
pos = pos->next;
}
if (pos == NULL) {
pos = MALLOC(sizeof(ReplyTrackData));
pos->next = NULL;
pos->localQueryOrigin = localOrigin;
if (localOrigin == NULL)
pos->queryOrigin = *origin;
pos->responseList = NULL;
if (prev == NULL)
rtdList = pos;
else
prev->next = pos;
}
TIME(&pos->lastReplyReceived);
rpos = pos->responseList;
rprev = NULL;
while (rpos != NULL) {
if (0 == memcmp(responder,
&rpos->responder,
sizeof(HostIdentity))) {
rpos->responseCount++;
MUTEX_UNLOCK(queryManagerLock);
return;
}
rprev = rpos;
rpos = rpos->next;
}
rpos = MALLOC(sizeof(ResponseList));
rpos->responseCount = 1;
rpos->responder = *responder;
rpos->next = NULL;
if (rprev == NULL)
pos->responseList = rpos;
else
rprev->next = rpos;
MUTEX_UNLOCK(queryManagerLock);
}
/**
* Map the id to an index into the bitmap array.
*/
static int getIndex(const HostIdentity * id) {
unsigned int index;
index = coreAPI->computeIndex(id);
if (index >= 8*BITMAP_SIZE)
index = index & (8*BITMAP_SIZE-1);
return index;
}
static void setBit(QueryRecord * qr,
int bit) {
unsigned char theBit = (1 << (bit & 7));
qr->bitmap[bit>>3] |= theBit;
}
static int getBit(QueryRecord * qr,
int bit) {
unsigned char theBit = (1 << (bit & 7));
return (qr->bitmap[bit>>3] & theBit) > 0;
}
/**
* Callback method for filling buffers. This method is invoked by the
* core if a message is about to be send and there is space left for a
* 3QUERY. We then search the pending queries and fill one (or more)
* in if possible.
*
* Note that the same query is not transmitted twice to a peer and that
* queries are not queued more frequently than 2 TTL_DECREMENT.
*
* @param receiver the receiver of the message
* @param position is the reference to the
* first unused position in the buffer where GNUnet is building
* the message
* @param padding is the number of bytes left in that buffer.
* @return the number of bytes written to
* that buffer (must be a positive number).
*/
static int fillInQuery(const HostIdentity * receiver,
void * position,
int padding) {
static unsigned int pos = 0;
unsigned int start;
unsigned int delta;
cron_t now;
cronTime(&now);
MUTEX_LOCK(queryManagerLock);
start = pos;
delta = 0;
while (padding - delta > sizeof(AFS_p2p_QUERY)+sizeof(HashCode160)) {
if ( (queries[pos].expires > now) &&
(getBit(&queries[pos],
getIndex(receiver)) == 0) &&
(padding - delta >=
ntohs(queries[pos].msg->header.size) ) ) {
#if DEBUG_QUERYMANAGER
EncName qenc;
EncName henc;
IFLOG(LOG_DEBUG,
hash2enc(&receiver->hashPubKey,
&henc);
hash2enc(&queries[pos].msg->queries[0],
&qenc));
LOG(LOG_DEBUG,
"adding %d queries (%s) to outbound buffer of %s\n",
NUMBER_OF_QUERIES(queries[pos].msg->header),
&qenc,
&henc);
#endif
setBit(&queries[pos],
getIndex(receiver));
memcpy(&((char*)position)[delta],
queries[pos].msg,
ntohs(queries[pos].msg->header.size));
queries[pos].sendCount++;
delta += ntohs(queries[pos].msg->header.size);
}
pos++;
if (pos >= QUERY_RECORD_COUNT)
pos = 0;
if (pos == start)
break;
}
MUTEX_UNLOCK(queryManagerLock);
return delta;
}
/**
* Initialize the query management.
*/
int initQueryManager() {
int i;
for (i=0;i