This schedules a search for information about the info-hash specified in
id. If port is not 0, it specifies the TCP port on which the current peer
-is litening; in that case, when the search is complete it will be announced
+is listening; in that case, when the search is complete it will be announced
to the network. The port is in host order, beware if you got it from
a struct sockaddr_in.
available, possibly in multiple pieces. The callback function will
additionally be called when the search is complete.
-Up to DHT_MAX_SEARCHES (20) searches can be in progress at a given time;
+Up to DHT_MAX_SEARCHES (1024) searches can be in progress at a given time;
any more, and dht_search will return -1. If you specify a new search for
the same info hash as a search still in progress, the previous search is
combined with the new one -- you will only receive a completion indication
request; this can be used to determine if the UDP port used for the DHT is
firewalled.
-If you want to display a single figure to the user, you should display good
-+ doubtful, which is the total number of nodes in your routing table. Some
-clients try to estimate the total number of nodes, but this doesn't make
-much sense -- since the result is exponential in the number of nodes in the
-routing table, small variations in the latter cause huge jumps in the
-former.
+If you want to display a single figure to the user, you should display
+good + doubtful, which is the total number of nodes in your routing table.
+Some clients try to estimate the total number of nodes, but this doesn't
+make much sense -- since the result is exponential in the number of nodes
+in the routing table, small variations in the latter cause huge jumps in
+the former.
* dht_get_nodes
Some of the code has had very little testing. If it breaks, you get to
keep both pieces.
-There is currently no good way to save and restore your routing table.
-
IPv6 support is deliberately not included: designing a double-stack
distributed hash table raises some tricky issues, and doing it naively may
break connectivity for everyone.
int done;
struct search_node nodes[SEARCH_NODES];
int numnodes;
+ struct search *next;
};
struct peer {
#define DHT_MAX_PEERS 2048
#endif
+/* The maximum number of searches we keep data about. */
+#ifndef DHT_MAX_SEARCHES
+#define DHT_MAX_SEARCHES 1024
+#endif
+
+/* The time after which we consider a search to be expirable. */
+#ifndef DHT_SEARCH_EXPIRE_TIME
+#define DHT_SEARCH_EXPIRE_TIME (62 * 60)
+#endif
+
struct storage {
unsigned char id[20];
int numpeers;
static struct bucket *buckets = NULL;
static struct storage *storage;
-/* The maximum number of concurrent searches. */
-#ifndef DHT_MAX_SEARCHES
-#define DHT_MAX_SEARCHES 20
-#endif
-
-static struct search searches[DHT_MAX_SEARCHES];
+static struct search *searches = NULL;
static int numsearches;
static unsigned short search_id;
}
n = n->next;
}
-
- if(!dubious && mybucket) {
+
+ /* If there's only one bucket, split even if there remain doubtful
+ nodes. This violates the spec, but it speeds up bootstrapping. */
+ if(mybucket && (!dubious || buckets->next == NULL)) {
debugf("Splitting.\n");
b = split_bucket(s, b);
mybucket_grow_time = now.tv_sec;
static struct search *
find_search(unsigned short tid)
{
- int i;
- for(i = 0; i < numsearches; i++) {
- if(searches[i].tid == tid)
- return &searches[i];
+ struct search *sr = searches;
+ while(sr) {
+ if(sr->tid == tid)
+ return sr;
+ sr = sr->next;
}
return NULL;
}
sr->numnodes--;
}
+static void
+expire_searches(void)
+{
+ struct search *sr = searches, *previous = NULL;
+
+ while(sr) {
+ struct search *next = sr->next;
+ if(sr->step_time < now.tv_sec - DHT_SEARCH_EXPIRE_TIME) {
+ if(previous)
+ previous->next = next;
+ else
+ searches = next;
+ free(sr);
+ numsearches--;
+ } else {
+ previous = sr;
+ }
+ sr = next;
+ }
+}
+
/* This must always return 0 or 1, never -1, not even on failure (see below). */
static int
search_send_get_peers(int s, struct search *sr, struct search_node *n)
}
static struct search *
-find_free_search_slot(void)
+new_search(void)
{
- int i;
- struct search *sr = NULL;
-
- if(numsearches < DHT_MAX_SEARCHES)
- return &searches[numsearches++];
-
- for(i = 0; i < numsearches; i++) {
- if(searches[i].done &&
- (sr == NULL || searches[i].step_time < sr->step_time))
- sr = &searches[i];
+ struct search *sr, *oldest = NULL;
+
+ /* Find the oldest done search */
+ sr = searches;
+ while(sr) {
+ if(sr->done &&
+ (oldest == NULL || oldest->step_time > sr->step_time))
+ oldest = sr;
+ sr = sr->next;
+ }
+
+ /* The oldest slot is expired. */
+ if(oldest && oldest->step_time < now.tv_sec - DHT_SEARCH_EXPIRE_TIME)
+ return oldest;
+
+ /* Allocate a new slot. */
+ if(numsearches < DHT_MAX_SEARCHES) {
+ sr = calloc(1, sizeof(struct search));
+ if(sr != NULL) {
+ sr->next = searches;
+ searches = sr;
+ numsearches++;
+ return sr;
+ }
}
- return sr;
+
+ /* Oh, well, never mind. Reuse the oldest slot. */
+ return oldest;
}
/* Insert the contents of a bucket into a search structure. */
{
struct search *sr;
struct bucket *b;
- int i;
- for(i = 0; i < numsearches; i++) {
- if(id_cmp(searches[i].id, id) == 0)
+ sr = searches;
+ while(sr) {
+ if(id_cmp(sr->id, id) == 0)
break;
+ sr = sr->next;
}
- if(i < numsearches) {
+ if(sr) {
/* We're reusing data from an old search. Reusing the same tid
means that we can merge replies for both searches. */
- int j;
- sr = searches + i;
+ int i;
sr->done = 0;
again:
- for(j = 0; j < sr->numnodes; j++) {
+ for(i = 0; i < sr->numnodes; i++) {
struct search_node *n;
- n = &sr->nodes[j];
+ n = &sr->nodes[i];
/* Discard any doubtful nodes. */
if(n->pinged >= 3 || n->reply_time < now.tv_sec - 7200) {
flush_search_node(n, sr);
n->acked = 0;
}
} else {
- sr = find_free_search_slot();
+ sr = new_search();
if(sr == NULL) {
errno = ENOSPC;
return -1;
}
- memset(sr, 0, sizeof(struct search));
sr->tid = search_id++;
+ sr->step_time = 0;
memcpy(sr->id, id, 20);
+ sr->done = 0;
sr->numnodes = 0;
}
b = find_bucket(id);
insert_search_bucket(b, sr);
- if(sr->numnodes < 8) {
+ if(sr->numnodes < SEARCH_NODES) {
struct bucket *p = previous_bucket(b);
if(b->next)
insert_search_bucket(b->next, sr);
static void
broken_node(int s, const unsigned char *id, struct sockaddr_in *sin)
{
- int i, j;
+ int i;
debugf("Blacklisting broken node.\n");
if(id) {
- /* Make the node easy to discard. */
struct node *n;
+ struct search *sr;
+ /* Make the node easy to discard. */
n = find_node(id);
if(n) {
n->pinged = 3;
pinged(s, n, NULL);
}
/* Discard it from any searches in progress. */
- for(i = 0; i < numsearches; i++) {
- for(j = 0; j < searches[i].numnodes; j++)
- if(id_cmp(searches[i].nodes[j].id, id) == 0)
- flush_search_node(&searches[i].nodes[j],
- &searches[i]);
+ sr = searches;
+ while(sr) {
+ for(i = 0; i < sr->numnodes; i++)
+ if(id_cmp(sr->nodes[i].id, id) == 0)
+ flush_search_node(&sr->nodes[i], sr);
+ sr = sr->next;
}
}
/* And make sure we don't hear from it again. */
if(cached_return)
*cached_return = cached;
if(incoming_return)
- *incoming_return = cached;
+ *incoming_return = incoming;
return good + dubious;
}
void
dht_dump_tables(FILE *f)
{
- int i, j;
+ int i;
struct bucket *b = buckets;
struct storage *st = storage;
+ struct search *sr = searches;
fprintf(f, "My id ");
print_hex(f, myid, 20);
}
b = b->next;
}
- for(i = 0; i < numsearches; i++) {
- struct search *sr = &searches[i];
- fprintf(f, "\nSearch %d id ", i);
+ while(sr) {
+ fprintf(f, "\nSearch id ");
print_hex(f, sr->id, 20);
fprintf(f, " age %d%s\n", (int)(now.tv_sec - sr->step_time),
sr->done ? " (done)" : "");
- for(j = 0; j < sr->numnodes; j++) {
- struct search_node *n = &sr->nodes[j];
- fprintf(f, "Node %d id ", j);
+ for(i = 0; i < sr->numnodes; i++) {
+ struct search_node *n = &sr->nodes[i];
+ fprintf(f, "Node %d id ", i);
print_hex(f, n->id, 20);
fprintf(f, " bits %d age ", common_bits(sr->id, n->id));
if(n->request_time)
find_node(n->id) ? " (known)" : "",
n->replied ? " (replied)" : "");
}
+ sr = sr->next;
}
if(buckets == NULL)
return -1;
+ searches = NULL;
numsearches = 0;
storage = NULL;
free(st->peers);
free(st);
}
-
+
+ while(searches) {
+ struct search *sr = searches;
+ searches = searches->next;
+ free(sr);
+ }
+
return 1;
}
debugf("Got reply to announce_peer.\n");
sr = find_search(ttid);
if(!sr) {
- debugf("Unknown search!");
+ debugf("Unknown search!\n");
new_node(s, id, &source, 1);
} else {
int i;
case PING:
debugf("Ping (%d)!\n", tid_len);
new_node(s, id, &source, 1);
- debugf("Sending pong!\n");
+ debugf("Sending pong.\n");
send_pong(s, (struct sockaddr*)&source, sizeof(source),
tid, tid_len);
break;
if(now.tv_sec >= expire_stuff_time) {
expire_buckets(s);
expire_storage();
+ expire_searches();
}
if(search_time > 0 && now.tv_sec >= search_time) {
- int i;
- for(i = 0; i < numsearches; i++) {
- struct search *sr = &searches[i];
+ struct search *sr;
+ sr = searches;
+ while(sr) {
if(!sr->done && sr->step_time + 5 <= now.tv_sec) {
search_step(s, sr, callback, closure);
}
+ sr = sr->next;
}
search_time = 0;
-
- for(i = 0; i < numsearches; i++) {
- struct search *sr = &searches[i];
+
+ sr = searches;
+ while(sr) {
if(!sr->done) {
time_t tm = sr->step_time + 15 + random() % 10;
if(search_time == 0 || search_time > tm)
search_time = tm;
}
+ sr = sr->next;
}
}