2364 lines
67 KiB
C
2364 lines
67 KiB
C
/*
|
|
Copyright (c) 2009 by Juliusz Chroboczek
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
/* Please, please, please.
|
|
|
|
You are welcome to integrate this code in your favourite Bittorrent
|
|
client. Please remember, however, that it is meant to be usable by
|
|
others, including myself. This means no C++, no relicensing, and no
|
|
gratuitious changes to the coding style. And please send back any
|
|
improvements to the author. */
|
|
|
|
/* For memmem. */
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/time.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include "dht.h"
|
|
|
|
#ifndef HAVE_MEMMEM
|
|
#ifdef __GLIBC__
|
|
#define HAVE_MEMMEM
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef MSG_CONFIRM
|
|
#define MSG_CONFIRM 0
|
|
#endif
|
|
|
|
/* We set sin_family to 0 to mark unused slots. */
|
|
#if AF_INET == 0
|
|
#error You lose
|
|
#endif
|
|
|
|
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
|
/* nothing */
|
|
#elif defined(__GNUC__)
|
|
#define inline __inline
|
|
#if (__GNUC__ >= 3)
|
|
#define restrict __restrict
|
|
#else
|
|
#define restrict /**/
|
|
#endif
|
|
#else
|
|
#define inline /**/
|
|
#define restrict /**/
|
|
#endif
|
|
|
|
#define MAX(x, y) ((x) >= (y) ? (x) : (y))
|
|
#define MIN(x, y) ((x) <= (y) ? (x) : (y))
|
|
|
|
struct node {
|
|
unsigned char id[20];
|
|
struct sockaddr_in sin;
|
|
time_t time; /* time of last message received */
|
|
time_t reply_time; /* time of last correct reply received */
|
|
time_t pinged_time; /* time of last request */
|
|
int pinged; /* how many requests we sent since last reply */
|
|
struct node *next;
|
|
};
|
|
|
|
struct bucket {
|
|
unsigned char first[20];
|
|
int count; /* number of nodes */
|
|
int time; /* time of last reply in this bucket */
|
|
struct node *nodes;
|
|
struct sockaddr_in cached; /* the address of a likely candidate */
|
|
struct bucket *next;
|
|
};
|
|
|
|
struct search_node {
|
|
unsigned char id[20];
|
|
struct sockaddr_in sin;
|
|
time_t request_time; /* the time of the last unanswered request */
|
|
time_t reply_time; /* the time of the last reply */
|
|
int pinged;
|
|
unsigned char token[40];
|
|
int token_len;
|
|
int replied; /* whether we have received a reply */
|
|
int acked; /* whether they acked our announcement */
|
|
};
|
|
|
|
/* When performing a search, we search for up to SEARCH_NODES closest nodes
|
|
to the destination, and use the additional ones to backtrack if any of
|
|
the target 8 turn out to be dead. */
|
|
#define SEARCH_NODES 14
|
|
|
|
struct search {
|
|
unsigned short tid;
|
|
time_t step_time; /* the time of the last search_step */
|
|
unsigned char id[20];
|
|
unsigned short port; /* 0 for pure searches */
|
|
int done;
|
|
struct search_node nodes[SEARCH_NODES];
|
|
int numnodes;
|
|
struct search *next;
|
|
};
|
|
|
|
struct peer {
|
|
time_t time;
|
|
unsigned char ip[4];
|
|
unsigned short port;
|
|
};
|
|
|
|
/* The maximum number of peers we store for a given hash. */
|
|
#ifndef DHT_MAX_PEERS
|
|
#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;
|
|
int maxpeers;
|
|
struct peer *peers;
|
|
struct storage *next;
|
|
};
|
|
|
|
static int send_ping(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len);
|
|
static int send_pong(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len);
|
|
static int send_find_node(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len,
|
|
const unsigned char *target, int confirm);
|
|
static int send_nodes_peers(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len,
|
|
const unsigned char *nodes, int nodes_len,
|
|
struct peer *peers1, int numpeers1,
|
|
struct peer *peers2, int numpeers2,
|
|
const unsigned char *token, int token_len);
|
|
static int send_closest_nodes(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len,
|
|
const unsigned char *id,
|
|
struct peer *peers1, int numpeers1,
|
|
struct peer *peers2, int numpeers2,
|
|
const unsigned char *token, int token_len);
|
|
static int send_get_peers(int s, struct sockaddr *sa, int salen,
|
|
unsigned char *tid, int tid_len,
|
|
unsigned char *infohash, int confirm);
|
|
static int send_announce_peer(int s, struct sockaddr *sa, int salen,
|
|
unsigned char *tid, int tid_len,
|
|
unsigned char *infohas, unsigned short port,
|
|
unsigned char *token, int token_len, int confirm);
|
|
int send_peer_announced(int s, struct sockaddr *sa, int salen,
|
|
unsigned char *tid, int tid_len);
|
|
|
|
#define REPLY 0
|
|
#define PING 1
|
|
#define FIND_NODE 2
|
|
#define GET_PEERS 3
|
|
#define ANNOUNCE_PEER 4
|
|
static int parse_message(const unsigned char *buf, int buflen,
|
|
unsigned char *tid_return, int *tid_len,
|
|
unsigned char *id_return,
|
|
unsigned char *info_hash_return,
|
|
unsigned char *target_return,
|
|
unsigned short *port_return,
|
|
unsigned char *token_return, int *token_len,
|
|
unsigned char *nodes_return, int *nodes_len,
|
|
const unsigned char *values_return, int *values_len);
|
|
|
|
static const unsigned char zeroes[20] = {0};
|
|
static const unsigned char ones[20] = {
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF
|
|
};
|
|
static time_t search_time;
|
|
static time_t confirm_nodes_time;
|
|
static time_t rotate_secrets_time;
|
|
|
|
static unsigned char myid[20];
|
|
static int have_v = 0;
|
|
static unsigned char my_v[9];
|
|
static unsigned char secret[8];
|
|
static unsigned char oldsecret[8];
|
|
|
|
static struct bucket *buckets = NULL;
|
|
static struct storage *storage;
|
|
|
|
static struct search *searches = NULL;
|
|
static int numsearches;
|
|
static unsigned short search_id;
|
|
|
|
/* The maximum number of nodes that we snub. There is probably little
|
|
reason to increase this value. */
|
|
#ifndef DHT_MAX_BLACKLISTED
|
|
#define DHT_MAX_BLACKLISTED 10
|
|
#endif
|
|
static struct sockaddr_in blacklist[DHT_MAX_BLACKLISTED];
|
|
int next_blacklisted;
|
|
|
|
static struct timeval now;
|
|
static time_t mybucket_grow_time;
|
|
static time_t expire_stuff_time;
|
|
|
|
#define MAX_LEAKY_BUCKET_TOKENS 40
|
|
static time_t leaky_bucket_time;
|
|
static int leaky_bucket_tokens;
|
|
|
|
FILE *dht_debug = NULL;
|
|
|
|
#ifdef __GNUC__
|
|
__attribute__ ((format (printf, 1, 2)))
|
|
#endif
|
|
static void
|
|
debugf(const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
if(dht_debug)
|
|
vfprintf(dht_debug, format, args);
|
|
va_end(args);
|
|
fflush(dht_debug);
|
|
}
|
|
|
|
static void
|
|
debug_printable(const unsigned char *buf, int buflen)
|
|
{
|
|
int i;
|
|
if(dht_debug) {
|
|
for(i = 0; i < buflen; i++)
|
|
putc(buf[i] >= 32 && buf[i] <= 126 ? buf[i] : '.', dht_debug);
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_hex(FILE *f, const unsigned char *buf, int buflen)
|
|
{
|
|
int i;
|
|
for(i = 0; i < buflen; i++)
|
|
fprintf(f, "%02x", buf[i]);
|
|
}
|
|
|
|
/* Forget about the ``XOR-metric''. An id is just a path from the
|
|
root of the tree, so bits are numbered from the start. */
|
|
|
|
static inline int
|
|
id_cmp(const unsigned char *restrict id1, const unsigned char *restrict id2)
|
|
{
|
|
/* Memcmp is guaranteed to perform an unsigned comparison. */
|
|
return memcmp(id1, id2, 20);
|
|
}
|
|
|
|
/* Find the lowest 1 bit in an id. */
|
|
static int
|
|
lowbit(const unsigned char *id)
|
|
{
|
|
int i, j;
|
|
for(i = 19; i >= 0; i--)
|
|
if(id[i] != 0)
|
|
break;
|
|
|
|
if(i < 0)
|
|
return -1;
|
|
|
|
for(j = 7; j >= 0; j--)
|
|
if((id[i] & (0x80 >> j)) != 0)
|
|
break;
|
|
|
|
return 8 * i + j;
|
|
}
|
|
|
|
/* Find how many bits two ids have in common. */
|
|
static int
|
|
common_bits(const unsigned char *id1, const unsigned char *id2)
|
|
{
|
|
int i, j;
|
|
unsigned char xor;
|
|
for(i = 0; i < 20; i++) {
|
|
if(id1[i] != id2[i])
|
|
break;
|
|
}
|
|
|
|
if(i == 20)
|
|
return 160;
|
|
|
|
xor = id1[i] ^ id2[i];
|
|
|
|
j = 0;
|
|
while((xor & 0x80) == 0) {
|
|
xor <<= 1;
|
|
j++;
|
|
}
|
|
|
|
return 8 * i + j;
|
|
}
|
|
|
|
/* Determine whether id1 or id2 is closer to ref */
|
|
static int
|
|
xorcmp(const unsigned char *id1, const unsigned char *id2,
|
|
const unsigned char *ref)
|
|
{
|
|
int i;
|
|
for(i = 0; i < 20; i++) {
|
|
unsigned char xor1, xor2;
|
|
if(id1[i] == id2[i])
|
|
continue;
|
|
xor1 = id1[i] ^ ref[i];
|
|
xor2 = id2[i] ^ ref[i];
|
|
if(xor1 < xor2)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* We keep buckets in a sorted linked list. A bucket b ranges from
|
|
b->first inclusive up to b->next->first exclusive. */
|
|
static int
|
|
in_bucket(const unsigned char *id, struct bucket *b)
|
|
{
|
|
return id_cmp(b->first, id) <= 0 &&
|
|
(b->next == NULL || id_cmp(id, b->next->first) < 0);
|
|
}
|
|
|
|
static struct bucket *
|
|
find_bucket(unsigned const char *id)
|
|
{
|
|
struct bucket *b = buckets;
|
|
|
|
while(1) {
|
|
if(b->next == NULL)
|
|
return b;
|
|
if(id_cmp(id, b->next->first) < 0)
|
|
return b;
|
|
b = b->next;
|
|
}
|
|
}
|
|
|
|
static struct bucket *
|
|
previous_bucket(struct bucket *b)
|
|
{
|
|
struct bucket *p = buckets;
|
|
|
|
if(b == p)
|
|
return NULL;
|
|
|
|
while(1) {
|
|
if(p->next == NULL)
|
|
return NULL;
|
|
if(p->next == b)
|
|
return p;
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
/* Every bucket contains an unordered list of nodes. */
|
|
static struct node *
|
|
find_node(const unsigned char *id)
|
|
{
|
|
struct bucket *b = find_bucket(id);
|
|
struct node *n;
|
|
|
|
if(b == NULL)
|
|
return NULL;
|
|
n = b->nodes;
|
|
while(n) {
|
|
if(id_cmp(n->id, id) == 0)
|
|
return n;
|
|
n = n->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Return a random node in a bucket. */
|
|
static struct node *
|
|
random_node(struct bucket *b)
|
|
{
|
|
struct node *n;
|
|
int nn;
|
|
|
|
if(b->count == 0)
|
|
return NULL;
|
|
|
|
nn = random() % b->count;
|
|
n = b->nodes;
|
|
while(nn > 0 && n) {
|
|
n = n->next;
|
|
nn--;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* Return the middle id of a bucket. */
|
|
static int
|
|
bucket_middle(struct bucket *b, unsigned char *id_return)
|
|
{
|
|
int bit1 = lowbit(b->first);
|
|
int bit2 = b->next ? lowbit(b->next->first) : -1;
|
|
int bit = MAX(bit1, bit2) + 1;
|
|
|
|
if(bit >= 160)
|
|
return -1;
|
|
|
|
memcpy(id_return, b->first, 20);
|
|
id_return[bit / 8] |= (0x80 >> (bit % 8));
|
|
return 1;
|
|
}
|
|
|
|
/* Return a random id within a bucket. */
|
|
static int
|
|
bucket_random(struct bucket *b, unsigned char *id_return)
|
|
{
|
|
int bit1 = lowbit(b->first);
|
|
int bit2 = b->next ? lowbit(b->next->first) : -1;
|
|
int bit = MAX(bit1, bit2) + 1;
|
|
int i;
|
|
|
|
if(bit >= 160) {
|
|
memcpy(id_return, b->first, 20);
|
|
return 1;
|
|
}
|
|
|
|
memcpy(id_return, b->first, bit / 8);
|
|
id_return[bit / 8] = b->first[bit / 8] & (0xFF00 >> (bit % 8));
|
|
id_return[bit / 8] |= random() & 0xFF >> (bit % 8);
|
|
for(i = bit / 8 + 1; i < 20; i++)
|
|
id_return[i] = random() & 0xFF;
|
|
return 1;
|
|
}
|
|
|
|
/* Insert a new node into a bucket. */
|
|
static struct node *
|
|
insert_node(struct node *node)
|
|
{
|
|
struct bucket *b = find_bucket(node->id);
|
|
|
|
node->next = b->nodes;
|
|
b->nodes = node;
|
|
b->count++;
|
|
return node;
|
|
}
|
|
|
|
/* This is our definition of a known-good node. */
|
|
static int
|
|
node_good(struct node *node)
|
|
{
|
|
return
|
|
node->pinged <= 2 &&
|
|
node->reply_time >= now.tv_sec - 7200 &&
|
|
node->time >= now.tv_sec - 900;
|
|
}
|
|
|
|
/* Our transaction-ids are 4-bytes long, with the first two bytes identi-
|
|
fying the kind of request, and the remaining two a sequence number in
|
|
host order. */
|
|
|
|
static void
|
|
make_tid(unsigned char *tid_return, const char *prefix, unsigned short seqno)
|
|
{
|
|
tid_return[0] = prefix[0] & 0xFF;
|
|
tid_return[1] = prefix[1] & 0xFF;
|
|
memcpy(tid_return + 2, &seqno, 2);
|
|
}
|
|
|
|
static int
|
|
tid_match(const unsigned char *tid, const char *prefix,
|
|
unsigned short *seqno_return)
|
|
{
|
|
if(tid[0] == (prefix[0] & 0xFF) && tid[1] == (prefix[1] & 0xFF)) {
|
|
if(seqno_return)
|
|
memcpy(seqno_return, tid + 2, 2);
|
|
return 1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
/* Every bucket caches the address of a likely node. Ping it. */
|
|
static int
|
|
send_cached_ping(int s, struct bucket *b)
|
|
{
|
|
int rc;
|
|
/* We set family to 0 when there's no cached node. */
|
|
if(b->cached.sin_family == AF_INET) {
|
|
unsigned char tid[4];
|
|
debugf("Sending ping to cached node.\n");
|
|
make_tid(tid, "pn", 0);
|
|
rc = send_ping(s, (struct sockaddr*)&b->cached,
|
|
sizeof(struct sockaddr_in),
|
|
tid, 4);
|
|
b->cached.sin_family = 0;
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Split a bucket into two equal parts. */
|
|
static struct bucket *
|
|
split_bucket(int s, struct bucket *b)
|
|
{
|
|
struct bucket *new;
|
|
struct node *nodes;
|
|
int rc;
|
|
unsigned char new_id[20];
|
|
|
|
rc = bucket_middle(b, new_id);
|
|
if(rc < 0)
|
|
return NULL;
|
|
|
|
new = calloc(1, sizeof(struct bucket));
|
|
if(new == NULL)
|
|
return NULL;
|
|
|
|
send_cached_ping(s, b);
|
|
|
|
memcpy(new->first, new_id, 20);
|
|
new->time = b->time;
|
|
|
|
nodes = b->nodes;
|
|
b->nodes = NULL;
|
|
b->count = 0;
|
|
new->next = b->next;
|
|
b->next = new;
|
|
while(nodes) {
|
|
struct node *n;
|
|
n = nodes;
|
|
nodes = nodes->next;
|
|
insert_node(n);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
/* Called whenever we send a request to a node. */
|
|
static void
|
|
pinged(int s, struct node *n, struct bucket *b)
|
|
{
|
|
n->pinged++;
|
|
n->pinged_time = now.tv_sec;
|
|
if(n->pinged >= 3)
|
|
send_cached_ping(s, b ? b : find_bucket(n->id));
|
|
}
|
|
|
|
/* We just learnt about a node, not necessarily a new one. Confirm is 1 if
|
|
the node sent a message, 2 if it sent us a reply. */
|
|
static struct node *
|
|
new_node(int s, const unsigned char *id, struct sockaddr_in *sin,
|
|
int confirm)
|
|
{
|
|
struct bucket *b = find_bucket(id);
|
|
struct node *n;
|
|
int mybucket = in_bucket(myid, b);
|
|
|
|
if(id_cmp(id, myid) == 0)
|
|
return NULL;
|
|
|
|
if(confirm == 2)
|
|
b->time = now.tv_sec;
|
|
|
|
n = b->nodes;
|
|
while(n) {
|
|
if(id_cmp(n->id, id) == 0) {
|
|
if(confirm || n->time < now.tv_sec - 15 * 60) {
|
|
/* Known node. Update stuff. */
|
|
n->sin = *sin;
|
|
if(confirm)
|
|
n->time = now.tv_sec;
|
|
if(confirm >= 2) {
|
|
n->reply_time = now.tv_sec;
|
|
n->pinged = 0;
|
|
n->pinged_time = 0;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
n = n->next;
|
|
}
|
|
|
|
/* New node. First, try to get rid of a known-bad node. */
|
|
n = b->nodes;
|
|
while(n) {
|
|
if(n->pinged >= 3 && n->pinged_time < now.tv_sec - 15) {
|
|
memcpy(n->id, id, 20);
|
|
n->sin = *sin;
|
|
n->time = confirm ? now.tv_sec : 0;
|
|
n->reply_time = confirm >= 2 ? now.tv_sec : 0;
|
|
n->pinged_time = 0;
|
|
n->pinged = 0;
|
|
if(mybucket)
|
|
mybucket_grow_time = now.tv_sec;
|
|
return n;
|
|
}
|
|
n = n->next;
|
|
}
|
|
|
|
if(b->count >= 8) {
|
|
/* Bucket full. Ping a dubious node */
|
|
int dubious = 0;
|
|
n = b->nodes;
|
|
while(n) {
|
|
/* Pick the first dubious node that we haven't pinged in the
|
|
last 15 seconds. This gives nodes the time to reply, but
|
|
tends to concentrate on the same nodes, so that we get rid
|
|
of bad nodes fast. */
|
|
if(!node_good(n)) {
|
|
dubious = 1;
|
|
if(n->pinged_time < now.tv_sec - 15) {
|
|
unsigned char tid[4];
|
|
debugf("Sending ping to dubious node.\n");
|
|
make_tid(tid, "pn", 0);
|
|
send_ping(s,
|
|
(struct sockaddr*)&n->sin,
|
|
sizeof(struct sockaddr_in),
|
|
tid, 4);
|
|
n->pinged++;
|
|
n->pinged_time = now.tv_sec;
|
|
break;
|
|
}
|
|
}
|
|
n = n->next;
|
|
}
|
|
|
|
/* 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;
|
|
return new_node(s, id, sin, confirm);
|
|
}
|
|
|
|
/* No space for this node. Cache it away for later. */
|
|
if(confirm || b->cached.sin_family == 0)
|
|
b->cached = *sin;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Create a new node. */
|
|
n = calloc(1, sizeof(struct node));
|
|
if(n == NULL)
|
|
return NULL;
|
|
memcpy(n->id, id, 20);
|
|
n->sin = *sin;
|
|
n->time = confirm ? now.tv_sec : 0;
|
|
n->reply_time = confirm >= 2 ? now.tv_sec : 0;
|
|
n->next = b->nodes;
|
|
b->nodes = n;
|
|
b->count++;
|
|
if(mybucket)
|
|
mybucket_grow_time = now.tv_sec;
|
|
return n;
|
|
}
|
|
|
|
/* Called periodically to purge known-bad nodes. Note that we're very
|
|
conservative here: broken nodes in the table don't do much harm, we'll
|
|
recover as soon as we find better ones. */
|
|
static int
|
|
expire_buckets(int s)
|
|
{
|
|
struct bucket *b = buckets;
|
|
|
|
while(b) {
|
|
struct node *n, *p;
|
|
int changed = 0;
|
|
|
|
while(b->nodes && b->nodes->pinged >= 4) {
|
|
n = b->nodes;
|
|
b->nodes = n->next;
|
|
b->count--;
|
|
changed = 1;
|
|
free(n);
|
|
}
|
|
|
|
p = b->nodes;
|
|
while(p) {
|
|
while(p->next && p->next->pinged >= 4) {
|
|
n = p->next;
|
|
p->next = n->next;
|
|
b->count--;
|
|
changed = 1;
|
|
free(n);
|
|
}
|
|
p = p->next;
|
|
}
|
|
|
|
if(changed)
|
|
send_cached_ping(s, b);
|
|
|
|
b = b->next;
|
|
}
|
|
expire_stuff_time = now.tv_sec + 120 + random() % 240;
|
|
return 1;
|
|
}
|
|
|
|
/* While a search is in progress, we don't necessarily keep the nodes being
|
|
walked in the main bucket table. A search in progress is identified by
|
|
a unique transaction id, a short (and hence small enough to fit in the
|
|
transaction id of the protocol packets). */
|
|
|
|
static struct search *
|
|
find_search(unsigned short tid)
|
|
{
|
|
struct search *sr = searches;
|
|
while(sr) {
|
|
if(sr->tid == tid)
|
|
return sr;
|
|
sr = sr->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* A search contains a list of nodes, sorted by decreasing distance to the
|
|
target. We just got a new candidate, insert it at the right spot or
|
|
discard it. */
|
|
|
|
static int
|
|
insert_search_node(unsigned char *id, struct sockaddr_in *sin,
|
|
struct search *sr, int replied,
|
|
unsigned char *token, int token_len)
|
|
{
|
|
struct search_node *n;
|
|
int i, j;
|
|
|
|
for(i = 0; i < sr->numnodes; i++) {
|
|
if(id_cmp(id, sr->nodes[i].id) == 0) {
|
|
n = &sr->nodes[i];
|
|
goto found;
|
|
}
|
|
if(xorcmp(id, sr->nodes[i].id, sr->id) < 0)
|
|
break;
|
|
}
|
|
|
|
if(i == SEARCH_NODES)
|
|
return 0;
|
|
|
|
if(sr->numnodes < SEARCH_NODES)
|
|
sr->numnodes++;
|
|
|
|
for(j = sr->numnodes - 1; j > i; j--) {
|
|
sr->nodes[j] = sr->nodes[j - 1];
|
|
}
|
|
|
|
n = &sr->nodes[i];
|
|
|
|
memset(n, 0, sizeof(struct search_node));
|
|
memcpy(n->id, id, 20);
|
|
|
|
found:
|
|
n->sin = *sin;
|
|
|
|
if(replied) {
|
|
n->replied = 1;
|
|
n->reply_time = now.tv_sec;
|
|
n->request_time = 0;
|
|
n->pinged = 0;
|
|
}
|
|
if(token) {
|
|
if(token_len >= 40) {
|
|
debugf("Eek! Overlong token.\n");
|
|
} else {
|
|
memcpy(n->token, token, token_len);
|
|
n->token_len = token_len;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
flush_search_node(struct search_node *n, struct search *sr)
|
|
{
|
|
int i = n - sr->nodes, j;
|
|
for(j = i; j < sr->numnodes - 1; j++)
|
|
sr->nodes[j] = sr->nodes[j + 1];
|
|
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)
|
|
{
|
|
struct node *node;
|
|
unsigned char tid[4];
|
|
|
|
if(n == NULL) {
|
|
int i;
|
|
for(i = 0; i < sr->numnodes; i++) {
|
|
if(sr->nodes[i].pinged < 3 && !sr->nodes[i].replied &&
|
|
sr->nodes[i].request_time < now.tv_sec - 15)
|
|
n = &sr->nodes[i];
|
|
}
|
|
}
|
|
|
|
if(!n || n->pinged >= 3 || n->replied ||
|
|
n->request_time >= now.tv_sec - 15)
|
|
return 0;
|
|
|
|
debugf("Sending get_peers.\n");
|
|
make_tid(tid, "gp", sr->tid);
|
|
send_get_peers(s, (struct sockaddr*)&n->sin,
|
|
sizeof(struct sockaddr_in), tid, 4, sr->id,
|
|
n->reply_time >= now.tv_sec - 15);
|
|
n->pinged++;
|
|
n->request_time = now.tv_sec;
|
|
/* If the node happens to be in our main routing table, mark it
|
|
as pinged. */
|
|
node = find_node(n->id);
|
|
if(node) pinged(s, node, NULL);
|
|
return 1;
|
|
}
|
|
|
|
/* When a search is in progress, we periodically call search_step to send
|
|
further requests. */
|
|
static void
|
|
search_step(int s, struct search *sr, dht_callback *callback, void *closure)
|
|
{
|
|
int i, j;
|
|
int all_done = 1;
|
|
|
|
/* Check if the first 8 live nodes have replied. */
|
|
j = 0;
|
|
for(i = 0; i < sr->numnodes && j < 8; i++) {
|
|
struct search_node *n = &sr->nodes[i];
|
|
if(n->pinged >= 3)
|
|
continue;
|
|
if(!n->replied) {
|
|
all_done = 0;
|
|
break;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
if(all_done) {
|
|
if(sr->port == 0) {
|
|
goto done;
|
|
} else {
|
|
int all_acked = 1;
|
|
j = 0;
|
|
for(i = 0; i < sr->numnodes && j < 8; i++) {
|
|
struct search_node *n = &sr->nodes[i];
|
|
struct node *node;
|
|
unsigned char tid[4];
|
|
if(n->pinged >= 3)
|
|
continue;
|
|
/* A proposed extension to the protocol consists in
|
|
omitting the token when storage tables are full. */
|
|
if(n->token_len == 0)
|
|
continue;
|
|
if(!n->acked) {
|
|
all_acked = 0;
|
|
debugf("Sending announce_peer.\n");
|
|
make_tid(tid, "ap", sr->tid);
|
|
send_announce_peer(s,
|
|
(struct sockaddr*)&n->sin,
|
|
sizeof(struct sockaddr_in),
|
|
tid, 4, sr->id, sr->port,
|
|
n->token, n->token_len,
|
|
n->reply_time >= now.tv_sec - 15);
|
|
n->pinged++;
|
|
n->request_time = now.tv_sec;
|
|
node = find_node(n->id);
|
|
if(node) pinged(s, node, NULL);
|
|
}
|
|
j++;
|
|
}
|
|
if(all_acked)
|
|
goto done;
|
|
}
|
|
sr->step_time = now.tv_sec;
|
|
return;
|
|
}
|
|
|
|
if(sr->step_time + 15 >= now.tv_sec)
|
|
return;
|
|
|
|
j = 0;
|
|
for(i = 0; i < sr->numnodes; i++) {
|
|
j += search_send_get_peers(s, sr, &sr->nodes[i]);
|
|
if(j >= 3)
|
|
break;
|
|
}
|
|
sr->step_time = now.tv_sec;
|
|
return;
|
|
|
|
done:
|
|
sr->done = 1;
|
|
if(callback)
|
|
(*callback)(closure, DHT_EVENT_SEARCH_DONE, sr->id, NULL, 0);
|
|
sr->step_time = now.tv_sec;
|
|
}
|
|
|
|
static struct search *
|
|
new_search(void)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Oh, well, never mind. Reuse the oldest slot. */
|
|
return oldest;
|
|
}
|
|
|
|
/* Insert the contents of a bucket into a search structure. */
|
|
static void
|
|
insert_search_bucket(struct bucket *b, struct search *sr)
|
|
{
|
|
struct node *n;
|
|
n = b->nodes;
|
|
while(n) {
|
|
insert_search_node(n->id, &n->sin, sr, 0, NULL, 0);
|
|
n = n->next;
|
|
}
|
|
}
|
|
|
|
/* Start a search. If port is non-zero, perform an announce when the
|
|
search is complete. */
|
|
int
|
|
dht_search(int s, const unsigned char *id, int port,
|
|
dht_callback *callback, void *closure)
|
|
{
|
|
struct search *sr;
|
|
struct bucket *b;
|
|
|
|
sr = searches;
|
|
while(sr) {
|
|
if(id_cmp(sr->id, id) == 0)
|
|
break;
|
|
sr = sr->next;
|
|
}
|
|
|
|
if(sr) {
|
|
/* We're reusing data from an old search. Reusing the same tid
|
|
means that we can merge replies for both searches. */
|
|
int i;
|
|
sr->done = 0;
|
|
again:
|
|
for(i = 0; i < sr->numnodes; i++) {
|
|
struct search_node *n;
|
|
n = &sr->nodes[i];
|
|
/* Discard any doubtful nodes. */
|
|
if(n->pinged >= 3 || n->reply_time < now.tv_sec - 7200) {
|
|
flush_search_node(n, sr);
|
|
goto again;
|
|
}
|
|
n->pinged = 0;
|
|
n->token_len = 0;
|
|
n->replied = 0;
|
|
n->acked = 0;
|
|
}
|
|
} else {
|
|
sr = new_search();
|
|
if(sr == NULL) {
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
sr->tid = search_id++;
|
|
sr->step_time = 0;
|
|
memcpy(sr->id, id, 20);
|
|
sr->done = 0;
|
|
sr->numnodes = 0;
|
|
}
|
|
|
|
sr->port = port;
|
|
|
|
b = find_bucket(id);
|
|
insert_search_bucket(b, sr);
|
|
|
|
if(sr->numnodes < SEARCH_NODES) {
|
|
struct bucket *p = previous_bucket(b);
|
|
if(b->next)
|
|
insert_search_bucket(b->next, sr);
|
|
if(p)
|
|
insert_search_bucket(p, sr);
|
|
}
|
|
if(sr->numnodes < SEARCH_NODES)
|
|
insert_search_bucket(find_bucket(myid), sr);
|
|
|
|
search_step(s, sr, callback, closure);
|
|
search_time = now.tv_sec;
|
|
return 1;
|
|
}
|
|
|
|
/* A struct storage stores all the stored peer addresses for a given info
|
|
hash. */
|
|
|
|
static struct storage *
|
|
find_storage(const unsigned char *id)
|
|
{
|
|
struct storage *st = storage;
|
|
|
|
while(st) {
|
|
if(id_cmp(id, st->id) == 0)
|
|
break;
|
|
st = st->next;
|
|
}
|
|
return st;
|
|
}
|
|
|
|
static int
|
|
storage_store(const unsigned char *id, const unsigned char *ip,
|
|
unsigned short port)
|
|
{
|
|
int i;
|
|
struct storage *st = storage;
|
|
|
|
st = find_storage(id);
|
|
|
|
if(st == NULL) {
|
|
st = calloc(1, sizeof(struct storage));
|
|
if(st == NULL) return -1;
|
|
memcpy(st->id, id, 20);
|
|
st->next = storage;
|
|
storage = st;
|
|
}
|
|
|
|
for(i = 0; i < st->numpeers; i++) {
|
|
if(st->peers[i].port == port && memcmp(st->peers[i].ip, ip, 4) == 0)
|
|
break;
|
|
}
|
|
if(i < st->numpeers) {
|
|
/* Already there, only need to refresh */
|
|
st->peers[i].time = now.tv_sec;
|
|
return 0;
|
|
} else {
|
|
struct peer *p;
|
|
if(i >= st->maxpeers) {
|
|
/* Need to expand the array. */
|
|
struct peer *new_peers;
|
|
int n;
|
|
if(st->maxpeers > DHT_MAX_PEERS / 2)
|
|
return 0;
|
|
n = st->maxpeers == 0 ? 2 : 2 * st->maxpeers;
|
|
new_peers = realloc(st->peers, n * sizeof(struct peer));
|
|
if(new_peers == NULL)
|
|
return -1;
|
|
st->peers = new_peers;
|
|
st->maxpeers = n;
|
|
}
|
|
p = &st->peers[st->numpeers++];
|
|
p->time = now.tv_sec;
|
|
memcpy(p->ip, ip, 4);
|
|
p->port = port;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
expire_storage(void)
|
|
{
|
|
struct storage *st = storage, *previous = NULL;
|
|
while(st) {
|
|
int i = 0;
|
|
while(i < st->numpeers) {
|
|
if(st->peers[i].time < now.tv_sec - 32 * 60) {
|
|
if(i != st->numpeers - 1)
|
|
st->peers[i] = st->peers[st->numpeers - 1];
|
|
st->numpeers--;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if(st->numpeers == 0) {
|
|
free(st->peers);
|
|
if(previous)
|
|
previous->next = st->next;
|
|
else
|
|
storage = st->next;
|
|
free(st);
|
|
if(previous)
|
|
st = previous->next;
|
|
else
|
|
st = storage;
|
|
} else {
|
|
previous = st;
|
|
st = st->next;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* We've just found out that a node is buggy. */
|
|
static void
|
|
broken_node(int s, const unsigned char *id, struct sockaddr_in *sin)
|
|
{
|
|
int i;
|
|
|
|
debugf("Blacklisting broken node.\n");
|
|
|
|
if(id) {
|
|
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. */
|
|
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. */
|
|
blacklist[next_blacklisted] = *sin;
|
|
next_blacklisted = (next_blacklisted + 1) % DHT_MAX_BLACKLISTED;
|
|
}
|
|
|
|
static int
|
|
rotate_secrets(void)
|
|
{
|
|
int rc;
|
|
|
|
rotate_secrets_time = now.tv_sec + 900 + random() % 1800;
|
|
|
|
memcpy(oldsecret, secret, sizeof(secret));
|
|
rc = dht_random_bytes(secret, sizeof(secret));
|
|
|
|
if(rc < 0)
|
|
return -1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifndef TOKEN_SIZE
|
|
#define TOKEN_SIZE 8
|
|
#endif
|
|
|
|
static void
|
|
make_token(const unsigned char *ipv4, unsigned short port, int old,
|
|
unsigned char *token_return)
|
|
{
|
|
dht_hash(token_return, TOKEN_SIZE,
|
|
old ? oldsecret : secret, sizeof(secret),
|
|
ipv4, 4,
|
|
(unsigned char*)&port, 2);
|
|
}
|
|
static int
|
|
token_match(unsigned char *token, int token_len,
|
|
const unsigned char *ipv4, unsigned short port)
|
|
{
|
|
unsigned char t[TOKEN_SIZE];
|
|
if(token_len != TOKEN_SIZE)
|
|
return 0;
|
|
make_token(ipv4, port, 0, t);
|
|
if(memcmp(t, token, TOKEN_SIZE) == 0)
|
|
return 1;
|
|
make_token(ipv4, port, 1, t);
|
|
if(memcmp(t, token, TOKEN_SIZE) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dht_nodes(int *good_return, int *dubious_return, int *cached_return,
|
|
int *incoming_return)
|
|
{
|
|
int good = 0, dubious = 0, cached = 0, incoming = 0;
|
|
struct bucket *b = buckets;
|
|
while(b) {
|
|
struct node *n = b->nodes;
|
|
while(n) {
|
|
if(node_good(n)) {
|
|
good++;
|
|
if(n->time > n->reply_time)
|
|
incoming++;
|
|
} else {
|
|
dubious++;
|
|
}
|
|
n = n->next;
|
|
}
|
|
if(b->cached.sin_family == AF_INET)
|
|
cached++;
|
|
b = b->next;
|
|
}
|
|
if(good_return)
|
|
*good_return = good;
|
|
if(dubious_return)
|
|
*dubious_return = dubious;
|
|
if(cached_return)
|
|
*cached_return = cached;
|
|
if(incoming_return)
|
|
*incoming_return = incoming;
|
|
return good + dubious;
|
|
}
|
|
|
|
|
|
void
|
|
dht_dump_tables(FILE *f)
|
|
{
|
|
int i;
|
|
struct bucket *b = buckets;
|
|
struct storage *st = storage;
|
|
struct search *sr = searches;
|
|
|
|
fprintf(f, "My id ");
|
|
print_hex(f, myid, 20);
|
|
fprintf(f, "\n");
|
|
while(b) {
|
|
struct node *n = b->nodes;
|
|
fprintf(f, "Bucket ");
|
|
print_hex(f, b->first, 20);
|
|
fprintf(f, " count %d age %d%s%s:\n",
|
|
b->count, (int)(now.tv_sec - b->time),
|
|
in_bucket(myid, b) ? " (mine)" : "",
|
|
b->cached.sin_family ? " (cached)" : "");
|
|
while(n) {
|
|
char buf[512];
|
|
fprintf(f, " Node ");
|
|
print_hex(f, n->id, 20);
|
|
inet_ntop(AF_INET, &n->sin.sin_addr, buf, 512);
|
|
fprintf(f, " %s:%d ", buf, ntohs(n->sin.sin_port));
|
|
if(n->time != n->reply_time)
|
|
fprintf(f, "age %ld, %ld",
|
|
(long)(now.tv_sec - n->time),
|
|
(long)(now.tv_sec - n->reply_time));
|
|
else
|
|
fprintf(f, "age %ld", (long)(now.tv_sec - n->time));
|
|
if(n->pinged)
|
|
fprintf(f, " (%d)", n->pinged);
|
|
if(node_good(n))
|
|
fprintf(f, " (good)");
|
|
fprintf(f, "\n");
|
|
n = n->next;
|
|
}
|
|
b = b->next;
|
|
}
|
|
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(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)
|
|
fprintf(f, "%d, ", (int)(now.tv_sec - n->request_time));
|
|
fprintf(f, "%d", (int)(now.tv_sec - n->reply_time));
|
|
if(n->pinged)
|
|
fprintf(f, " (%d)", n->pinged);
|
|
fprintf(f, "%s%s.\n",
|
|
find_node(n->id) ? " (known)" : "",
|
|
n->replied ? " (replied)" : "");
|
|
}
|
|
sr = sr->next;
|
|
}
|
|
|
|
|
|
while(st) {
|
|
fprintf(f, "\nStorage ");
|
|
print_hex(f, st->id, 20);
|
|
fprintf(f, " %d/%d nodes:", st->numpeers, st->maxpeers);
|
|
for(i = 0; i < st->numpeers; i++) {
|
|
char buf[20];
|
|
inet_ntop(AF_INET, st->peers[i].ip, buf, 20);
|
|
fprintf(f, " %s:%u (%ld)",
|
|
buf, st->peers[i].port,
|
|
(long)(now.tv_sec - st->peers[i].time));
|
|
}
|
|
st = st->next;
|
|
}
|
|
|
|
fprintf(f, "\n\n");
|
|
fflush(f);
|
|
}
|
|
|
|
int
|
|
dht_init(int s, const unsigned char *id, const unsigned char *v)
|
|
{
|
|
int rc;
|
|
|
|
if(buckets) {
|
|
errno = EBUSY;
|
|
return -1;
|
|
}
|
|
|
|
buckets = calloc(sizeof(struct bucket), 1);
|
|
if(buckets == NULL)
|
|
return -1;
|
|
|
|
searches = NULL;
|
|
numsearches = 0;
|
|
|
|
storage = NULL;
|
|
|
|
rc = fcntl(s, F_GETFL, 0);
|
|
if(rc < 0)
|
|
goto fail;
|
|
|
|
rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK));
|
|
if(rc < 0)
|
|
goto fail;
|
|
|
|
memcpy(myid, id, 20);
|
|
if(v) {
|
|
memcpy(my_v, "1:v4:", 5);
|
|
memcpy(my_v + 5, v, 4);
|
|
have_v = 1;
|
|
} else {
|
|
have_v = 0;
|
|
}
|
|
|
|
gettimeofday(&now, NULL);
|
|
|
|
mybucket_grow_time = now.tv_sec;
|
|
confirm_nodes_time = now.tv_sec + random() % 3;
|
|
|
|
search_id = random() & 0xFFFF;
|
|
search_time = 0;
|
|
|
|
next_blacklisted = 0;
|
|
|
|
leaky_bucket_time = now.tv_sec;
|
|
leaky_bucket_tokens = MAX_LEAKY_BUCKET_TOKENS;
|
|
|
|
memset(secret, 0, sizeof(secret));
|
|
rc = rotate_secrets();
|
|
if(rc < 0)
|
|
goto fail;
|
|
|
|
expire_buckets(s);
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
free(buckets);
|
|
buckets = NULL;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
dht_uninit(int s, int dofree)
|
|
{
|
|
if(!dofree)
|
|
return 1;
|
|
|
|
while(buckets) {
|
|
struct bucket *b = buckets;
|
|
buckets = b->next;
|
|
while(b->nodes) {
|
|
struct node *n = b->nodes;
|
|
b->nodes = n->next;
|
|
free(n);
|
|
}
|
|
free(b);
|
|
}
|
|
|
|
while(storage) {
|
|
struct storage *st = storage;
|
|
storage = storage->next;
|
|
free(st->peers);
|
|
free(st);
|
|
}
|
|
|
|
while(searches) {
|
|
struct search *sr = searches;
|
|
searches = searches->next;
|
|
free(sr);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Rate control for requests we receive. */
|
|
|
|
static int
|
|
leaky_bucket(void)
|
|
{
|
|
if(leaky_bucket_tokens == 0) {
|
|
leaky_bucket_tokens = MIN(MAX_LEAKY_BUCKET_TOKENS,
|
|
4 * (now.tv_sec - leaky_bucket_time));
|
|
leaky_bucket_time = now.tv_sec;
|
|
}
|
|
|
|
if(leaky_bucket_tokens == 0)
|
|
return 0;
|
|
|
|
leaky_bucket_tokens--;
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
dht_periodic(int s, int available, time_t *tosleep,
|
|
dht_callback *callback, void *closure)
|
|
{
|
|
gettimeofday(&now, NULL);
|
|
|
|
if(available) {
|
|
int rc, i, message;
|
|
unsigned char tid[16], id[20], info_hash[20], target[20];
|
|
unsigned char buf[1536], nodes[256], token[128];
|
|
int tid_len = 16, token_len = 128;
|
|
int nodes_len = 256;
|
|
unsigned short port;
|
|
unsigned char values[2048];
|
|
int values_len = 2048;
|
|
struct sockaddr_in source;
|
|
socklen_t source_len = sizeof(struct sockaddr_in);
|
|
unsigned short ttid;
|
|
|
|
rc = recvfrom(s, buf, 1536, 0,
|
|
(struct sockaddr*)&source, &source_len);
|
|
if(rc < 0) {
|
|
if(errno == EAGAIN)
|
|
goto dontread;
|
|
else
|
|
return rc;
|
|
}
|
|
|
|
if(source_len != sizeof(struct sockaddr_in)) {
|
|
/* Hmm... somebody gave us an IPv6 socket. */
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
for(i = 0; i < DHT_MAX_BLACKLISTED; i++) {
|
|
if(blacklist[i].sin_family == AF_INET &&
|
|
blacklist[i].sin_port == source.sin_port &&
|
|
memcmp(&blacklist[i].sin_addr, &source.sin_addr, 4) == 0) {
|
|
debugf("Received packet from blacklisted node.\n");
|
|
goto dontread;
|
|
}
|
|
}
|
|
|
|
/* There's a bug in parse_message -- it will happily overflow the
|
|
buffer if it's not NUL-terminated. For now, put a NUL at the
|
|
end of buffers. */
|
|
|
|
if(rc < 1536) {
|
|
buf[rc] = '\0';
|
|
} else {
|
|
debugf("Overlong message.\n");
|
|
goto dontread;
|
|
}
|
|
|
|
message = parse_message(buf, rc, tid, &tid_len, id, info_hash,
|
|
target, &port, token, &token_len,
|
|
nodes, &nodes_len, values, &values_len);
|
|
if(id_cmp(id, zeroes) == 0) {
|
|
debugf("Message with no id: ");
|
|
debug_printable(buf, rc);
|
|
debugf("\n");
|
|
goto dontread;
|
|
}
|
|
|
|
if(id_cmp(id, myid) == 0) {
|
|
debugf("Received message from self.\n");
|
|
goto dontread;
|
|
}
|
|
|
|
if(message > REPLY) {
|
|
/* Rate limit requests. */
|
|
if(!leaky_bucket()) {
|
|
debugf("Dropping request due to rate limiting.\n");
|
|
goto dontread;
|
|
}
|
|
}
|
|
|
|
switch(message) {
|
|
case REPLY:
|
|
if(tid_len != 4) {
|
|
debugf("Broken node truncates transaction ids: ");
|
|
debug_printable(buf, rc);
|
|
printf("\n");
|
|
/* This is really annoying, as it means that we will
|
|
time-out all our searches that go through this node.
|
|
Kill it. */
|
|
broken_node(s, id, &source);
|
|
goto dontread;
|
|
}
|
|
if(tid_match(tid, "pn", NULL)) {
|
|
debugf("Pong!\n");
|
|
new_node(s, id, &source, 2);
|
|
} else if(tid_match(tid, "fn", NULL) ||
|
|
tid_match(tid, "gp", NULL)) {
|
|
int gp = 0;
|
|
struct search *sr = NULL;
|
|
if(tid_match(tid, "gp", &ttid)) {
|
|
gp = 1;
|
|
sr = find_search(ttid);
|
|
}
|
|
debugf("Nodes found (%d)%s!\n", nodes_len / 26,
|
|
gp ? " for get_peers" : "");
|
|
if(nodes_len % 26 != 0) {
|
|
debugf("Unexpected length for node info!\n");
|
|
broken_node(s, id, &source);
|
|
} else if(gp && sr == NULL) {
|
|
debugf("Unknown search!\n");
|
|
new_node(s, id, &source, 1);
|
|
} else {
|
|
int i;
|
|
new_node(s, id, &source, 2);
|
|
for(i = 0; i < nodes_len / 26; i++) {
|
|
unsigned char *ni = nodes + i * 26;
|
|
struct sockaddr_in sin;
|
|
if(id_cmp(ni, myid) == 0)
|
|
continue;
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin_family = AF_INET;
|
|
memcpy(&sin.sin_addr, ni + 20, 4);
|
|
memcpy(&sin.sin_port, ni + 24, 2);
|
|
new_node(s, ni, &sin, 0);
|
|
if(sr) {
|
|
insert_search_node(ni, &sin, sr, 0, NULL, 0);
|
|
}
|
|
}
|
|
if(sr)
|
|
/* Since we received a reply, the number of
|
|
requests in flight has decreased. Let's push
|
|
another request. */
|
|
search_send_get_peers(s, sr, NULL);
|
|
}
|
|
if(sr) {
|
|
insert_search_node(id, &source, sr,
|
|
1, token, token_len);
|
|
if(values_len > 0) {
|
|
debugf("Got values (%d)!\n", values_len / 6);
|
|
if(callback) {
|
|
(*callback)(closure, DHT_EVENT_VALUES,
|
|
sr->id, (void*)values, values_len);
|
|
}
|
|
}
|
|
}
|
|
} else if(tid_match(tid, "ap", &ttid)) {
|
|
struct search *sr;
|
|
debugf("Got reply to announce_peer.\n");
|
|
sr = find_search(ttid);
|
|
if(!sr) {
|
|
debugf("Unknown search!\n");
|
|
new_node(s, id, &source, 1);
|
|
} else {
|
|
int i;
|
|
new_node(s, id, &source, 2);
|
|
for(i = 0; i < sr->numnodes; i++)
|
|
if(id_cmp(sr->nodes[i].id, id) == 0) {
|
|
sr->nodes[i].request_time = 0;
|
|
sr->nodes[i].reply_time = now.tv_sec;
|
|
sr->nodes[i].acked = 1;
|
|
sr->nodes[i].pinged = 0;
|
|
break;
|
|
}
|
|
/* See comment for gp above. */
|
|
search_send_get_peers(s, sr, NULL);
|
|
}
|
|
} else {
|
|
debugf("Unexpected reply: ");
|
|
debug_printable(buf, rc);
|
|
debugf("\n");
|
|
}
|
|
break;
|
|
case PING:
|
|
debugf("Ping (%d)!\n", tid_len);
|
|
new_node(s, id, &source, 1);
|
|
debugf("Sending pong.\n");
|
|
send_pong(s, (struct sockaddr*)&source, sizeof(source),
|
|
tid, tid_len);
|
|
break;
|
|
case FIND_NODE:
|
|
debugf("Find node!\n");
|
|
new_node(s, id, &source, 1);
|
|
debugf("Sending closest nodes.\n");
|
|
send_closest_nodes(s, (struct sockaddr*)&source, sizeof(source),
|
|
tid, tid_len, target,
|
|
NULL, 0, NULL, 0, NULL, 0);
|
|
break;
|
|
case GET_PEERS:
|
|
debugf("Get_peers!\n");
|
|
new_node(s, id, &source, 1);
|
|
if(id_cmp(info_hash, zeroes) == 0) {
|
|
debugf("Eek! Got get_peers with no info_hash.\n");
|
|
break;
|
|
} else {
|
|
struct storage *st = find_storage(info_hash);
|
|
unsigned char token[TOKEN_SIZE];
|
|
make_token((unsigned char*)&source.sin_addr,
|
|
ntohs(source.sin_port),
|
|
0, token);
|
|
if(st && st->numpeers > 0) {
|
|
int i0, n0, n1;
|
|
i0 = random() % st->numpeers;
|
|
/* We treat peers as a circular list, and choose 50
|
|
peers starting at i0. */
|
|
n0 = MIN(st->numpeers - i0, 50);
|
|
n1 = n0 >= 50 ? 0 : MIN(50, i0);
|
|
debugf("Sending found peers (%d).\n", n0 + n1);
|
|
/* According to the spec, we should not be sending any
|
|
nodes in this case. However, this avoids breaking
|
|
searches if data is stored at the wrong place, and
|
|
is also what libtorrent and uTorrent do. */
|
|
send_closest_nodes(s, (struct sockaddr*)&source,
|
|
sizeof(source), tid, tid_len,
|
|
info_hash,
|
|
st->peers + i0, n0,
|
|
st->peers, n1,
|
|
token, TOKEN_SIZE);
|
|
} else {
|
|
debugf("Sending nodes for get_peers.\n");
|
|
send_closest_nodes(s, (struct sockaddr*)&source,
|
|
sizeof(source),
|
|
tid, tid_len, info_hash,
|
|
NULL, 0, NULL, 0,
|
|
token, TOKEN_SIZE);
|
|
}
|
|
}
|
|
break;
|
|
case ANNOUNCE_PEER:
|
|
debugf("Announce peer!\n");
|
|
new_node(s, id, &source, 1);
|
|
if(id_cmp(info_hash, zeroes) == 0) {
|
|
debugf("Announce_peer with no info_hash.\n");
|
|
break;
|
|
}
|
|
if(!token_match(token, token_len,
|
|
(unsigned char*)&source.sin_addr,
|
|
ntohs(source.sin_port))) {
|
|
debugf("Incorrect token for announce_peer.\n");
|
|
break;
|
|
}
|
|
if(port == 0) {
|
|
debugf("Announce_peer with forbidden port %d.\n", port);
|
|
break;
|
|
}
|
|
storage_store(info_hash,
|
|
(unsigned char*)&source.sin_addr, port);
|
|
debugf("Sending peer announced.\n");
|
|
send_peer_announced(s, (struct sockaddr*)&source,
|
|
sizeof(source), tid, tid_len);
|
|
}
|
|
}
|
|
|
|
dontread:
|
|
if(now.tv_sec >= rotate_secrets_time)
|
|
rotate_secrets();
|
|
|
|
if(now.tv_sec >= expire_stuff_time) {
|
|
expire_buckets(s);
|
|
expire_storage();
|
|
expire_searches();
|
|
}
|
|
|
|
if(search_time > 0 && now.tv_sec >= search_time) {
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if(now.tv_sec >= confirm_nodes_time) {
|
|
struct bucket *b;
|
|
int soon = 0;
|
|
b = buckets;
|
|
while(!soon && b) {
|
|
struct bucket *q;
|
|
if(b->time < now.tv_sec - 900) {
|
|
/* This bucket hasn't seen any activity for a long
|
|
time. Pick a random id in this bucket's range, and
|
|
send a request to a random node. */
|
|
unsigned char id[20];
|
|
struct node *n;
|
|
int rc;
|
|
|
|
rc = bucket_random(b, id);
|
|
if(rc < 0)
|
|
memcpy(id, b->first, 20);
|
|
|
|
q = b;
|
|
/* If the bucket is empty, we try to fill it from
|
|
a neighbour. We also sometimes do it gratuitiously
|
|
to recover from buckets full of broken nodes. */
|
|
if(q->next && (q->count == 0 || random() % 7 == 0))
|
|
q = b->next;
|
|
if(q->count == 0 || random() % 7 == 0) {
|
|
struct bucket *r;
|
|
r = previous_bucket(b);
|
|
if(r && r->count > 0)
|
|
q = r;
|
|
}
|
|
|
|
if(q) {
|
|
n = random_node(q);
|
|
if(n) {
|
|
unsigned char tid[4];
|
|
debugf("Sending find_node "
|
|
"for bucket maintenance.\n");
|
|
make_tid(tid, "fn", 0);
|
|
send_find_node(s, (struct sockaddr*)&n->sin,
|
|
sizeof(struct sockaddr_in),
|
|
tid, 4, id,
|
|
n->reply_time >= now.tv_sec - 15);
|
|
pinged(s, n, q);
|
|
/* In order to avoid sending queries back-to-back,
|
|
give up for now and reschedule us soon. */
|
|
soon = 1;
|
|
}
|
|
}
|
|
}
|
|
b = b->next;
|
|
}
|
|
|
|
if(!soon && mybucket_grow_time >= now.tv_sec - 150) {
|
|
/* We've seen updates to our own bucket recently. Try to
|
|
improve our neighbourship. */
|
|
unsigned char id[20];
|
|
struct bucket *b, *q;
|
|
struct node *n;
|
|
|
|
memcpy(id, myid, 20);
|
|
id[19] = random() % 0xFF;
|
|
b = find_bucket(myid);
|
|
q = b;
|
|
if(q->next && (q->count == 0 || random() % 7 == 0))
|
|
q = b->next;
|
|
if(q->count == 0 || random() % 7 == 0) {
|
|
struct bucket *r;
|
|
r = previous_bucket(b);
|
|
if(r && r->count > 0)
|
|
q = r;
|
|
}
|
|
|
|
if(q) {
|
|
n = random_node(q);
|
|
if(n) {
|
|
unsigned char tid[4];
|
|
debugf("Sending find_node "
|
|
"for neighborhood maintenance.\n");
|
|
make_tid(tid, "fn", 0);
|
|
send_find_node(s, (struct sockaddr*)&n->sin,
|
|
sizeof(struct sockaddr_in),
|
|
tid, 4, id,
|
|
n->reply_time >= now.tv_sec - 15);
|
|
pinged(s, n, q);
|
|
}
|
|
}
|
|
soon = 1;
|
|
}
|
|
|
|
/* In order to maintain all buckets' age within 900 seconds, worst
|
|
case is roughly 40 seconds, assuming the table is 22 bits deep.
|
|
We want to keep a margin for neighborhood maintenance, so keep
|
|
this within 30 seconds. */
|
|
if(soon)
|
|
confirm_nodes_time = now.tv_sec + 10 + random() % 20;
|
|
else
|
|
confirm_nodes_time = now.tv_sec + 60 + random() % 120;
|
|
}
|
|
|
|
if(confirm_nodes_time > now.tv_sec)
|
|
*tosleep = confirm_nodes_time - now.tv_sec;
|
|
else
|
|
*tosleep = 0;
|
|
|
|
if(search_time > 0) {
|
|
if(search_time <= now.tv_sec)
|
|
*tosleep = 0;
|
|
else if(*tosleep > search_time - now.tv_sec)
|
|
*tosleep = search_time - now.tv_sec;
|
|
}
|
|
|
|
return find_bucket(myid)->count > 2;
|
|
}
|
|
|
|
int
|
|
dht_get_nodes(struct sockaddr_in *sins, int num)
|
|
{
|
|
int i;
|
|
struct bucket *b;
|
|
struct node *n;
|
|
|
|
i = 0;
|
|
|
|
/* For restoring to work without discarding too many nodes, the list
|
|
must start with the contents of our bucket. */
|
|
b = find_bucket(myid);
|
|
n = b->nodes;
|
|
while(n && i < num) {
|
|
if(node_good(n)) {
|
|
sins[i] = n->sin;
|
|
i++;
|
|
}
|
|
n = n->next;
|
|
}
|
|
|
|
b = buckets;
|
|
while(b && i < num) {
|
|
if(!in_bucket(myid, b)) {
|
|
n = b->nodes;
|
|
while(n && i < num) {
|
|
if(node_good(n)) {
|
|
sins[i] = n->sin;
|
|
i++;
|
|
}
|
|
n = n->next;
|
|
}
|
|
}
|
|
b = b->next;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
int
|
|
dht_insert_node(int s, const unsigned char *id, struct sockaddr_in *sin)
|
|
{
|
|
struct node *n;
|
|
n = new_node(s, id, sin, 0);
|
|
return !!n;
|
|
}
|
|
|
|
int
|
|
dht_ping_node(int s, struct sockaddr_in *sin)
|
|
{
|
|
unsigned char tid[4];
|
|
debugf("Sending ping.\n");
|
|
make_tid(tid, "pn", 0);
|
|
return send_ping(s, (struct sockaddr*)sin, sizeof(struct sockaddr_in),
|
|
tid, 4);
|
|
}
|
|
|
|
/* We could use a proper bencoding printer and parser, but the format of
|
|
DHT messages is fairly stylised, so this seemed simpler. */
|
|
|
|
#define CHECK(offset, delta, size) \
|
|
if(delta < 0 || offset + delta > size) goto fail
|
|
|
|
#define INC(offset, delta, size) \
|
|
CHECK(offset, delta, size); \
|
|
offset += delta
|
|
|
|
#define COPY(buf, offset, src, delta, size) \
|
|
CHECK(offset, delta, size); \
|
|
memcpy(buf + offset, src, delta); \
|
|
i += delta;
|
|
|
|
#define ADD_V(buf, offset, size) \
|
|
if(have_v) { \
|
|
COPY(buf, offset, my_v, sizeof(my_v), size); \
|
|
}
|
|
|
|
int
|
|
send_ping(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len)
|
|
{
|
|
char buf[512];
|
|
int i = 0, rc;
|
|
rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
|
|
COPY(buf, i, myid, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "e1:q4:ping1:t%d:", tid_len);
|
|
INC(i, rc, 512);
|
|
COPY(buf, i, tid, tid_len, 512);
|
|
ADD_V(buf, i, 512);
|
|
rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
|
|
return sendto(s, buf, i, 0, sa, salen);
|
|
|
|
fail:
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
send_pong(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len)
|
|
{
|
|
char buf[512];
|
|
int i = 0, rc;
|
|
rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
|
|
COPY(buf, i, myid, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "e1:t%d:", tid_len); INC(i, rc, 512);
|
|
COPY(buf, i, tid, tid_len, 512);
|
|
ADD_V(buf, i, 512);
|
|
rc = snprintf(buf + i, 512 - i, "1:y1:re"); INC(i, rc, 512);
|
|
return sendto(s, buf, i, 0, sa, salen);
|
|
|
|
fail:
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
send_find_node(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len,
|
|
const unsigned char *target, int confirm)
|
|
{
|
|
char buf[512];
|
|
int i = 0, rc;
|
|
rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
|
|
COPY(buf, i, myid, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "6:target20:"); INC(i, rc, 512);
|
|
COPY(buf, i, target, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "e1:q9:find_node1:t%d:", tid_len);
|
|
INC(i, rc, 512);
|
|
COPY(buf, i, tid, tid_len, 512);
|
|
ADD_V(buf, i, 512);
|
|
rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
|
|
return sendto(s, buf, i, confirm ? MSG_CONFIRM : 0, sa, salen);
|
|
|
|
fail:
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
send_nodes_peers(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len,
|
|
const unsigned char *nodes, int nodes_len,
|
|
struct peer *peers1, int numpeers1,
|
|
struct peer *peers2, int numpeers2,
|
|
const unsigned char *token, int token_len)
|
|
{
|
|
char buf[2048];
|
|
int i = 0, rc, j;
|
|
rc = snprintf(buf + i, 2048 - i, "d1:rd2:id20:"); INC(i, rc, 2048);
|
|
COPY(buf, i, myid, 20, 2048);
|
|
if(token_len > 0) {
|
|
rc = snprintf(buf + i, 2048 - i, "5:token%d:", token_len);
|
|
INC(i, rc, 2048);
|
|
COPY(buf, i, token, token_len, 2048);
|
|
}
|
|
if(nodes_len > 0) {
|
|
rc = snprintf(buf + i, 2048 - i, "5:nodes%d:", nodes_len);
|
|
INC(i, rc, 2048);
|
|
COPY(buf, i, nodes, nodes_len, 2048);
|
|
}
|
|
for(j = 0; j < numpeers1; j++) {
|
|
unsigned short swapped = htons(peers1[j].port);
|
|
rc = snprintf(buf + i, 2048 - i, "6:"); INC(i, rc, 2048);
|
|
COPY(buf, i, peers1[j].ip, 4, 2048);
|
|
COPY(buf, i, &swapped, 2, 2048);
|
|
}
|
|
for(j = 0; j < numpeers2; j++) {
|
|
unsigned short swapped = htons(peers2[j].port);
|
|
rc = snprintf(buf + i, 2048 - i, "6:"); INC(i, rc, 2048);
|
|
COPY(buf, i, peers2[j].ip, 4, 2048);
|
|
COPY(buf, i, &swapped, 2, 2048);
|
|
}
|
|
rc = snprintf(buf + i, 2048 - i, "e1:t%d:", tid_len); INC(i, rc, 2048);
|
|
COPY(buf, i, tid, tid_len, 2048);
|
|
ADD_V(buf, i, 2048);
|
|
rc = snprintf(buf + i, 2048 - i, "1:y1:re"); INC(i, rc, 2048);
|
|
|
|
return sendto(s, buf, i, 0, sa, salen);
|
|
|
|
fail:
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
insert_closest_node(unsigned char *nodes, int numnodes,
|
|
const unsigned char *id, struct node *n)
|
|
{
|
|
int i;
|
|
for(i = 0; i< numnodes; i++) {
|
|
if(id_cmp(nodes + 26 * i, id) == 0)
|
|
return numnodes;
|
|
if(xorcmp(n->id, nodes + 26 * i, id) < 0)
|
|
break;
|
|
}
|
|
|
|
if(i == 8)
|
|
return numnodes;
|
|
|
|
if(numnodes < 8)
|
|
numnodes++;
|
|
|
|
if(i < numnodes - 1)
|
|
memmove(nodes + 26 * (i + 1), nodes + 26 * i, 26 * (numnodes - i - 1));
|
|
|
|
memcpy(nodes + 26 * i, n->id, 20);
|
|
memcpy(nodes + 26 * i + 20, &n->sin.sin_addr, 4);
|
|
memcpy(nodes + 26 * i + 24, &n->sin.sin_port, 2);
|
|
|
|
return numnodes;
|
|
}
|
|
|
|
static int
|
|
buffer_closest_nodes(unsigned char *nodes, int numnodes,
|
|
const unsigned char *id, struct bucket *b)
|
|
{
|
|
struct node *n = b->nodes;
|
|
while(n) {
|
|
if(node_good(n))
|
|
numnodes = insert_closest_node(nodes, numnodes, id, n);
|
|
n = n->next;
|
|
}
|
|
return numnodes;
|
|
}
|
|
|
|
int
|
|
send_closest_nodes(int s, struct sockaddr *sa, int salen,
|
|
const unsigned char *tid, int tid_len,
|
|
const unsigned char *id,
|
|
struct peer *peers1, int numpeers1,
|
|
struct peer *peers2, int numpeers2,
|
|
const unsigned char *token, int token_len)
|
|
{
|
|
unsigned char nodes[8 * 26];
|
|
int numnodes = 0;
|
|
struct bucket *b;
|
|
|
|
b = find_bucket(id);
|
|
numnodes = buffer_closest_nodes(nodes, numnodes, id, b);
|
|
if(b->next)
|
|
numnodes = buffer_closest_nodes(nodes, numnodes, id, b->next);
|
|
b = previous_bucket(b);
|
|
if(b)
|
|
numnodes = buffer_closest_nodes(nodes, numnodes, id, b);
|
|
|
|
return send_nodes_peers(s, sa, salen, tid, tid_len,
|
|
nodes, numnodes * 26,
|
|
peers1, numpeers1, peers2, numpeers2,
|
|
token, token_len);
|
|
}
|
|
|
|
int
|
|
send_get_peers(int s, struct sockaddr *sa, int salen,
|
|
unsigned char *tid, int tid_len, unsigned char *infohash,
|
|
int confirm)
|
|
{
|
|
char buf[512];
|
|
int i = 0, rc;
|
|
|
|
rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
|
|
COPY(buf, i, myid, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "9:info_hash20:"); INC(i, rc, 512);
|
|
COPY(buf, i, infohash, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "e1:q9:get_peers1:t%d:", tid_len);
|
|
INC(i, rc, 512);
|
|
COPY(buf, i, tid, tid_len, 512);
|
|
ADD_V(buf, i, 512);
|
|
rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
|
|
return sendto(s, buf, i, confirm ? MSG_CONFIRM : 0, sa, salen);
|
|
|
|
fail:
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
send_announce_peer(int s, struct sockaddr *sa, int salen,
|
|
unsigned char *tid, int tid_len,
|
|
unsigned char *infohash, unsigned short port,
|
|
unsigned char *token, int token_len, int confirm)
|
|
{
|
|
char buf[512];
|
|
int i = 0, rc;
|
|
|
|
rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512);
|
|
COPY(buf, i, myid, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "9:info_hash20:"); INC(i, rc, 512);
|
|
COPY(buf, i, infohash, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "4:porti%ue5:token%d:", (unsigned)port,
|
|
token_len);
|
|
INC(i, rc, 512);
|
|
COPY(buf, i, token, token_len, 512);
|
|
rc = snprintf(buf + i, 512 - i, "e1:q13:announce_peer1:t%d:", tid_len);
|
|
INC(i, rc, 512);
|
|
COPY(buf, i, tid, tid_len, 512);
|
|
ADD_V(buf, i, 512);
|
|
rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512);
|
|
|
|
return sendto(s, buf, i, confirm ? 0 : MSG_CONFIRM, sa, salen);
|
|
|
|
fail:
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
send_peer_announced(int s, struct sockaddr *sa, int salen,
|
|
unsigned char *tid, int tid_len)
|
|
{
|
|
char buf[512];
|
|
int i = 0, rc;
|
|
|
|
rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512);
|
|
COPY(buf, i, myid, 20, 512);
|
|
rc = snprintf(buf + i, 512 - i, "e1:t%d:", tid_len);
|
|
INC(i, rc, 512);
|
|
COPY(buf, i, tid, tid_len, 512);
|
|
ADD_V(buf, i, 2048);
|
|
rc = snprintf(buf + i, 2048 - i, "1:y1:re"); INC(i, rc, 2048);
|
|
return sendto(s, buf, i, 0, sa, salen);
|
|
|
|
fail:
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
#undef CHECK
|
|
#undef INC
|
|
#undef COPY
|
|
#undef ADD_V
|
|
|
|
#ifndef HAVE_MEMMEM
|
|
static void *
|
|
memmem(const void *haystack, size_t haystacklen,
|
|
const void *needle, size_t needlelen)
|
|
{
|
|
const char *h = haystack;
|
|
const char *n = needle;
|
|
size_t i;
|
|
|
|
/* size_t is unsigned */
|
|
if(needlelen > haystacklen)
|
|
return NULL;
|
|
|
|
for(i = 0; i <= haystacklen - needlelen; i++) {
|
|
if(memcmp(h + i, n, needlelen) == 0)
|
|
return (void*)(h + i);
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
parse_message(const unsigned char *buf, int buflen,
|
|
unsigned char *tid_return, int *tid_len,
|
|
unsigned char *id_return, unsigned char *info_hash_return,
|
|
unsigned char *target_return, unsigned short *port_return,
|
|
unsigned char *token_return, int *token_len,
|
|
unsigned char *nodes_return, int *nodes_len,
|
|
const unsigned char *values_return, int *values_len)
|
|
{
|
|
const unsigned char *p;
|
|
|
|
#define CHECK(ptr, len) \
|
|
if(((unsigned char*)ptr) + (len) > (buf) + (buflen)) goto overflow;
|
|
|
|
if(tid_return) {
|
|
p = memmem(buf, buflen, "1:t", 3);
|
|
if(p) {
|
|
long l;
|
|
char *q;
|
|
l = strtol((char*)p + 3, &q, 10);
|
|
if(q && *q == ':' && l > 0 && l < *tid_len) {
|
|
CHECK(q + 1, l);
|
|
memcpy(tid_return, q + 1, l);
|
|
*tid_len = l;
|
|
} else
|
|
*tid_len = 0;
|
|
}
|
|
}
|
|
if(id_return) {
|
|
p = memmem(buf, buflen, "2:id20:", 7);
|
|
if(p) {
|
|
CHECK(p + 7, 20);
|
|
memcpy(id_return, p + 7, 20);
|
|
} else {
|
|
memset(id_return, 0, 20);
|
|
}
|
|
}
|
|
if(info_hash_return) {
|
|
p = memmem(buf, buflen, "9:info_hash20:", 14);
|
|
if(p) {
|
|
CHECK(p + 14, 20);
|
|
memcpy(info_hash_return, p + 14, 20);
|
|
} else {
|
|
memset(info_hash_return, 0, 20);
|
|
}
|
|
}
|
|
if(port_return) {
|
|
p = memmem(buf, buflen, "porti", 5);
|
|
if(p) {
|
|
long l;
|
|
char *q;
|
|
l = strtol((char*)p + 5, &q, 10);
|
|
if(q && *q == 'e' && l > 0 && l < 0x10000)
|
|
*port_return = l;
|
|
else
|
|
*port_return = 0;
|
|
} else
|
|
*port_return = 0;
|
|
}
|
|
if(target_return) {
|
|
p = memmem(buf, buflen, "6:target20:", 11);
|
|
if(p) {
|
|
CHECK(p + 11, 20);
|
|
memcpy(target_return, p + 11, 20);
|
|
} else {
|
|
memset(target_return, 0, 20);
|
|
}
|
|
}
|
|
if(token_return) {
|
|
p = memmem(buf, buflen, "5:token", 7);
|
|
if(p) {
|
|
long l;
|
|
char *q;
|
|
l = strtol((char*)p + 7, &q, 10);
|
|
if(q && *q == ':' && l > 0 && l < *token_len) {
|
|
CHECK(q + 1, l);
|
|
memcpy(token_return, q + 1, l);
|
|
*token_len = l;
|
|
} else
|
|
*token_len = 0;
|
|
} else
|
|
*token_len = 0;
|
|
}
|
|
|
|
if(nodes_return) {
|
|
p = memmem(buf, buflen, "5:nodes", 7);
|
|
if(p) {
|
|
long l;
|
|
char *q;
|
|
l = strtol((char*)p + 7, &q, 10);
|
|
if(q && *q == ':' && l > 0 && l < *nodes_len) {
|
|
CHECK(q + 1, l);
|
|
memcpy(nodes_return, q + 1, l);
|
|
*nodes_len = l;
|
|
} else
|
|
*nodes_len = 0;
|
|
} else
|
|
*nodes_len = 0;
|
|
}
|
|
|
|
if(values_return) {
|
|
p = memmem(buf, buflen, "6:valuesl", 9);
|
|
if(p) {
|
|
int i = p - buf + 9;
|
|
int j = 0;
|
|
while(1) {
|
|
long l;
|
|
char *q;
|
|
l = strtol((char*)buf + i, &q, 10);
|
|
if(q && *q == ':' && l > 0) {
|
|
CHECK(q + 1, l);
|
|
if(j + l > *values_len)
|
|
break;
|
|
i = q + 1 + l - (char*)buf;
|
|
/* BEP 32 allows heterogeneous values -- ignore IPv6 */
|
|
if(l != 6) {
|
|
debugf("Received weird value -- %d bytes.\n",
|
|
(int)l);
|
|
continue;
|
|
}
|
|
memcpy((char*)values_return + j, q + 1, l);
|
|
j += l;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if(i >= buflen || buf[i] != 'e')
|
|
debugf("eek... unexpected end for values.\n");
|
|
*values_len = j;
|
|
} else {
|
|
*values_len = 0;
|
|
}
|
|
}
|
|
|
|
#undef CHECK
|
|
|
|
if(memmem(buf, buflen, "1:y1:r", 6))
|
|
return REPLY;
|
|
if(!memmem(buf, buflen, "1:y1:q", 6))
|
|
return -1;
|
|
if(memmem(buf, buflen, "1:q4:ping", 9))
|
|
return PING;
|
|
if(memmem(buf, buflen, "1:q9:find_node", 14))
|
|
return FIND_NODE;
|
|
if(memmem(buf, buflen, "1:q9:get_peers", 14))
|
|
return GET_PEERS;
|
|
if(memmem(buf, buflen, "1:q13:announce_peer", 19))
|
|
return ANNOUNCE_PEER;
|
|
return -1;
|
|
|
|
overflow:
|
|
debugf("Truncated message.\n");
|
|
return -1;
|
|
}
|