From 8724e697e746df6513bb2032bb1aa9b41ed6e842 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Fri, 13 Nov 2009 16:13:43 +0100 Subject: [PATCH 1/2] Import the IPv6 version of the DHT. --- third-party/dht/CHANGES | 4 + third-party/dht/README | 15 +- third-party/dht/dht-example.c | 93 +++- third-party/dht/dht.c | 1172 +++++++++++++++++++++++++++-------------- third-party/dht/dht.h | 22 +- 5 files changed, 851 insertions(+), 455 deletions(-) diff --git a/third-party/dht/CHANGES b/third-party/dht/CHANGES index e7ea6ee..a766ff7 100644 --- a/third-party/dht/CHANGES +++ b/third-party/dht/CHANGES @@ -1,3 +1,7 @@ +dht-0.11 (unreleased) + + * Implement IPv6 support (BEP-32). + 18 October 2009: dht-0.10 * Send nodes even when sending values. This is a violation of the diff --git a/third-party/dht/README b/third-party/dht/README index 20ec926..7ce69d3 100644 --- a/third-party/dht/README +++ b/third-party/dht/README @@ -147,14 +147,14 @@ Functions provided by you * The callback function The callback function is called with 5 arguments. Closure is simply the -value that you passed to dht_periodic. Event is one of DHT_EVENT_VALUES, -which indicates that we have new values, or DHT_EVENT_SEARCH_DONE, which -indicates that a search has completed. In either case, info_hash is set to -the info-hash of the search. +value that you passed to dht_periodic. Event is one of DHT_EVENT_VALUES or +DHT_EVENT_VALUES6, which indicates that we have new values, or +DHT_EVENT_SEARCH_DONE or DHT_EVENT_SEARCH_DONE6, which indicates that +a search has completed. In either case, info_hash is set to the info-hash +of the search. In the case of DHT_EVENT_VALUES, data is a list of nodes in ``compact'' -format -- 6 bytes per node, 4 for the IP address and 2 for the port. It's -length in bytes is in data_len. +format -- 6 or 18 bytes per node. Its length in bytes is in data_len. * dht_hash @@ -186,9 +186,6 @@ make most full cone NATs happy. Some of the code has had very little testing. If it breaks, you get to keep both pieces. -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. Juliusz Chroboczek diff --git a/third-party/dht/dht-example.c b/third-party/dht/dht-example.c index deb442b..be46a91 100644 --- a/third-party/dht/dht-example.c +++ b/third-party/dht/dht-example.c @@ -20,7 +20,7 @@ #include "dht.h" #define MAX_BOOTSTRAP_NODES 20 -static struct sockaddr_in bootstrap_nodes[MAX_BOOTSTRAP_NODES]; +static struct sockaddr_storage bootstrap_nodes[MAX_BOOTSTRAP_NODES]; static int num_bootstrap_nodes = 0; static volatile sig_atomic_t dumping = 0, searching = 0, exiting = 0; @@ -92,7 +92,7 @@ int main(int argc, char **argv) { int i, rc, fd; - int s, port; + int s, s6, port; int have_id = 0; unsigned char myid[20]; time_t tosleep = 0; @@ -146,9 +146,8 @@ main(int argc, char **argv) while(i < argc) { struct addrinfo hints, *info, *infop; memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; - rc = getaddrinfo(argv[i], NULL, &hints, &info); + rc = getaddrinfo(argv[i], argv[i + 1], &hints, &info); if(rc != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc)); exit(1); @@ -160,14 +159,10 @@ main(int argc, char **argv) infop = info; while(infop) { - if(infop->ai_addr->sa_family == AF_INET) { - struct sockaddr_in sin; - memcpy(&sin, infop->ai_addr, infop->ai_addrlen); - sin.sin_port = htons(atoi(argv[i])); - bootstrap_nodes[num_bootstrap_nodes] = sin; - num_bootstrap_nodes++; - } + memcpy(&bootstrap_nodes[num_bootstrap_nodes], + infop->ai_addr, infop->ai_addrlen); infop = infop->ai_next; + num_bootstrap_nodes++; } freeaddrinfo(info); @@ -178,28 +173,62 @@ main(int argc, char **argv) be logged. */ dht_debug = stdout; - /* We need an IPv4 socket, bound to a stable port. Rumour has it that - uTorrent works better when it is the same as your Bittorrent port. */ + /* We need an IPv4 and an IPv6 socket, bound to a stable port. Rumour + has it that uTorrent works better when it is the same as your + Bittorrent port. */ s = socket(PF_INET, SOCK_DGRAM, 0); if(s < 0) { - perror("socket"); - exit(1); + perror("socket(IPv4)"); } - { + s6 = socket(PF_INET6, SOCK_DGRAM, 0); + if(s < 0) { + perror("socket(IPv6)"); + } + + if(s < 0 && s6 < 0) + exit(1); + + + if(s >= 0) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(port); rc = bind(s, (struct sockaddr*)&sin, sizeof(sin)); if(rc < 0) { - perror("bind"); + perror("bind(IPv4)"); + exit(1); + } + } + + if(s6 >= 0) { + struct sockaddr_in6 sin6; + int rc; + int val = 1; + + rc = setsockopt(s6, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&val, sizeof(val)); + if(rc < 0) { + perror("setsockopt(IPV6_V6ONLY)"); + exit(1); + } + + /* BEP-32 actually mandates that we should bind this socket to one + of our global IPv6 addresses. Never mind for this example. */ + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + rc = bind(s6, (struct sockaddr*)&sin6, sizeof(sin6)); + if(rc < 0) { + perror("bind(IPv6)"); exit(1); } } /* Init the dht. This sets the socket into non-blocking mode. */ - rc = dht_init(s, myid, NULL); + rc = dht_init(s, s6, myid, NULL); if(rc < 0) { perror("dht_init"); exit(1); @@ -217,7 +246,8 @@ main(int argc, char **argv) a dump) and you already know their ids, it's better to use dht_insert_node. If the ids are incorrect, the DHT will recover. */ for(i = 0; i < num_bootstrap_nodes; i++) { - dht_ping_node(s, &bootstrap_nodes[i]); + dht_ping_node((struct sockaddr*)&bootstrap_nodes[i], + sizeof(bootstrap_nodes[i])); usleep(random() % 100000); } @@ -228,8 +258,11 @@ main(int argc, char **argv) tv.tv_usec = random() % 1000000; FD_ZERO(&readfds); - FD_SET(s, &readfds); - rc = select(s + 1, &readfds, NULL, NULL, &tv); + if(s >= 0) + FD_SET(s, &readfds); + if(s6 >= 0) + FD_SET(s6, &readfds); + rc = select(s > s6 ? s + 1 : s6 + 1, &readfds, NULL, NULL, &tv); if(rc < 0) { if(errno != EINTR) { perror("select"); @@ -240,7 +273,7 @@ main(int argc, char **argv) if(exiting) break; - rc = dht_periodic(s, rc > 0, &tosleep, callback, NULL); + rc = dht_periodic(rc > 0, &tosleep, callback, NULL); if(rc < 0) { if(errno == EINTR) { continue; @@ -253,11 +286,14 @@ main(int argc, char **argv) } /* This is how you trigger a search for a torrent hash. If port - (the third argument) is non-zero, it also performs an announce. + (the second argument) is non-zero, it also performs an announce. Since peers expire announced data after 30 minutes, it's a good idea to reannounce every 28 minutes or so. */ if(searching) { - dht_search(s, hash, 0, callback, NULL); + if(s >= 0) + dht_search(hash, 0, AF_INET, callback, NULL); + if(s6 >= 0) + dht_search(hash, 0, AF_INET6, callback, NULL); searching = 0; } @@ -269,13 +305,14 @@ main(int argc, char **argv) } { - struct sockaddr_in sins[500]; + struct sockaddr_in sin[500]; + int num = 500, num6 = 0; int i; - i = dht_get_nodes(sins, 500); - printf("Found %d good nodes.\n", i); + i = dht_get_nodes(sin, &num, NULL, &num6); + printf("Found %d (%d) good nodes.\n", i, num); } - dht_uninit(s, 1); + dht_uninit(1); return 0; usage: diff --git a/third-party/dht/dht.c b/third-party/dht/dht.c index b19ae69..fefbbaf 100644 --- a/third-party/dht/dht.c +++ b/third-party/dht/dht.c @@ -57,7 +57,7 @@ THE SOFTWARE. #endif /* We set sin_family to 0 to mark unused slots. */ -#if AF_INET == 0 +#if AF_INET == 0 || AF_INET6 == 0 #error You lose #endif @@ -80,7 +80,8 @@ THE SOFTWARE. struct node { unsigned char id[20]; - struct sockaddr_in sin; + struct sockaddr_storage ss; + int sslen; 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 */ @@ -89,17 +90,20 @@ struct node { }; struct bucket { + int af; 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 sockaddr_storage cached; /* the address of a likely candidate */ + int cachedlen; struct bucket *next; }; struct search_node { unsigned char id[20]; - struct sockaddr_in sin; + struct sockaddr_storage ss; + int sslen; time_t request_time; /* the time of the last unanswered request */ time_t reply_time; /* the time of the last reply */ int pinged; @@ -116,6 +120,7 @@ struct search_node { struct search { unsigned short tid; + int af; time_t step_time; /* the time of the last search_step */ unsigned char id[20]; unsigned short port; /* 0 for pure searches */ @@ -127,7 +132,8 @@ struct search { struct peer { time_t time; - unsigned char ip[4]; + unsigned char ip[16]; + unsigned short len; unsigned short port; }; @@ -148,39 +154,37 @@ struct peer { struct storage { unsigned char id[20]; - int numpeers; - int maxpeers; + int numpeers, maxpeers; struct peer *peers; struct storage *next; }; -static int send_ping(int s, struct sockaddr *sa, int salen, +static int send_ping(struct sockaddr *sa, int salen, const unsigned char *tid, int tid_len); -static int send_pong(int s, struct sockaddr *sa, int salen, +static int send_pong(struct sockaddr *sa, int salen, const unsigned char *tid, int tid_len); -static int send_find_node(int s, struct sockaddr *sa, int salen, +static int send_find_node(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 *target, int want, int confirm); +static int send_nodes_peers(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 *nodes6, int nodes6_len, + int af, struct storage *st, const unsigned char *token, int token_len); -static int send_closest_nodes(int s, struct sockaddr *sa, int salen, +static int send_closest_nodes(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 *id, int want, + int af, struct storage *st, const unsigned char *token, int token_len); -static int send_get_peers(int s, struct sockaddr *sa, int salen, +static int send_get_peers(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 *infohash, int want, int confirm); +static int send_announce_peer(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, +int send_peer_announced(struct sockaddr *sa, int salen, unsigned char *tid, int tid_len); #define REPLY 0 @@ -188,6 +192,10 @@ int send_peer_announced(int s, struct sockaddr *sa, int salen, #define FIND_NODE 2 #define GET_PEERS 3 #define ANNOUNCE_PEER 4 + +#define WANT4 1 +#define WANT6 2 + static int parse_message(const unsigned char *buf, int buflen, unsigned char *tid_return, int *tid_len, unsigned char *id_return, @@ -196,7 +204,10 @@ static int parse_message(const unsigned char *buf, int buflen, 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); + unsigned char *nodes6_return, int *nodes6_len, + unsigned char *values_return, int *values_len, + unsigned char *values6_return, int *values6_len, + int *want_return); static const unsigned char zeroes[20] = {0}; static const unsigned char ones[20] = { @@ -204,6 +215,10 @@ static const unsigned char ones[20] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + +static int dht_socket = -1; +static int dht_socket6 = -1; + static time_t search_time; static time_t confirm_nodes_time; static time_t rotate_secrets_time; @@ -215,6 +230,7 @@ static unsigned char secret[8]; static unsigned char oldsecret[8]; static struct bucket *buckets = NULL; +static struct bucket *buckets6 = NULL; static struct storage *storage; static struct search *searches = NULL; @@ -226,11 +242,11 @@ static unsigned short search_id; #ifndef DHT_MAX_BLACKLISTED #define DHT_MAX_BLACKLISTED 10 #endif -static struct sockaddr_in blacklist[DHT_MAX_BLACKLISTED]; +static struct sockaddr_storage blacklist[DHT_MAX_BLACKLISTED]; int next_blacklisted; static struct timeval now; -static time_t mybucket_grow_time; +static time_t mybucket_grow_time, mybucket6_grow_time; static time_t expire_stuff_time; #define MAX_LEAKY_BUCKET_TOKENS 40 @@ -355,9 +371,9 @@ in_bucket(const unsigned char *id, struct bucket *b) } static struct bucket * -find_bucket(unsigned const char *id) +find_bucket(unsigned const char *id, int af) { - struct bucket *b = buckets; + struct bucket *b = af == AF_INET ? buckets : buckets6; while(1) { if(b->next == NULL) @@ -371,7 +387,7 @@ find_bucket(unsigned const char *id) static struct bucket * previous_bucket(struct bucket *b) { - struct bucket *p = buckets; + struct bucket *p = b->af == AF_INET ? buckets : buckets6; if(b == p) return NULL; @@ -387,9 +403,9 @@ previous_bucket(struct bucket *b) /* Every bucket contains an unordered list of nodes. */ static struct node * -find_node(const unsigned char *id) +find_node(const unsigned char *id, int af) { - struct bucket *b = find_bucket(id); + struct bucket *b = find_bucket(id, af); struct node *n; if(b == NULL) @@ -458,13 +474,13 @@ bucket_random(struct bucket *b, unsigned char *id_return) 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); + struct bucket *b = find_bucket(node->id, node->ss.ss_family); node->next = b->nodes; b->nodes = node; @@ -508,18 +524,17 @@ tid_match(const unsigned char *tid, const char *prefix, /* Every bucket caches the address of a likely node. Ping it. */ static int -send_cached_ping(int s, struct bucket *b) +send_cached_ping(struct bucket *b) { int rc; /* We set family to 0 when there's no cached node. */ - if(b->cached.sin_family == AF_INET) { + if(b->cached.ss_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; + rc = send_ping((struct sockaddr*)&b->cached, b->cachedlen, tid, 4); + b->cached.ss_family = 0; + b->cachedlen = 0; return rc; } return 0; @@ -527,7 +542,7 @@ send_cached_ping(int s, struct bucket *b) /* Split a bucket into two equal parts. */ static struct bucket * -split_bucket(int s, struct bucket *b) +split_bucket(struct bucket *b) { struct bucket *new; struct node *nodes; @@ -542,7 +557,9 @@ split_bucket(int s, struct bucket *b) if(new == NULL) return NULL; - send_cached_ping(s, b); + new->af = b->af; + + send_cached_ping(b); memcpy(new->first, new_id, 20); new->time = b->time; @@ -563,21 +580,20 @@ split_bucket(int s, struct bucket *b) /* Called whenever we send a request to a node. */ static void -pinged(int s, struct node *n, struct bucket *b) +pinged(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)); + send_cached_ping(b ? b : find_bucket(n->id, n->ss.ss_family)); } /* 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) +new_node(const unsigned char *id, struct sockaddr *sa, int salen, int confirm) { - struct bucket *b = find_bucket(id); + struct bucket *b = find_bucket(id, sa->sa_family); struct node *n; int mybucket = in_bucket(myid, b); @@ -592,7 +608,7 @@ new_node(int s, const unsigned char *id, struct sockaddr_in *sin, if(id_cmp(n->id, id) == 0) { if(confirm || n->time < now.tv_sec - 15 * 60) { /* Known node. Update stuff. */ - n->sin = *sin; + memcpy((struct sockaddr*)&n->ss, sa, salen); if(confirm) n->time = now.tv_sec; if(confirm >= 2) { @@ -611,13 +627,17 @@ new_node(int s, const unsigned char *id, struct sockaddr_in *sin, while(n) { if(n->pinged >= 3 && n->pinged_time < now.tv_sec - 15) { memcpy(n->id, id, 20); - n->sin = *sin; + memcpy((struct sockaddr*)&n->ss, sa, salen); 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; + if(mybucket) { + if(sa->sa_family == AF_INET) + mybucket_grow_time = now.tv_sec; + else + mybucket6_grow_time = now.tv_sec; + } return n; } n = n->next; @@ -638,9 +658,7 @@ new_node(int s, const unsigned char *id, struct sockaddr_in *sin, 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), + send_ping((struct sockaddr*)&n->ss, n->sslen, tid, 4); n->pinged++; n->pinged_time = now.tv_sec; @@ -654,14 +672,19 @@ new_node(int s, const unsigned char *id, struct sockaddr_in *sin, 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); + b = split_bucket(b); + if(sa->sa_family == AF_INET) + mybucket_grow_time = now.tv_sec; + else + mybucket6_grow_time = now.tv_sec; + return new_node(id, sa, salen, confirm); } /* No space for this node. Cache it away for later. */ - if(confirm || b->cached.sin_family == 0) - b->cached = *sin; + if(confirm || b->cached.ss_family == 0) { + memcpy(&b->cached, sa, salen); + b->cachedlen = salen; + } return NULL; } @@ -671,14 +694,19 @@ new_node(int s, const unsigned char *id, struct sockaddr_in *sin, if(n == NULL) return NULL; memcpy(n->id, id, 20); - n->sin = *sin; + memcpy(&n->ss, sa, salen); + n->sslen = salen; 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; + if(mybucket) { + if(sa->sa_family == AF_INET) + mybucket_grow_time = now.tv_sec; + else + mybucket6_grow_time = now.tv_sec; + } return n; } @@ -686,10 +714,8 @@ new_node(int s, const unsigned char *id, struct sockaddr_in *sin, 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) +expire_buckets(struct bucket *b) { - struct bucket *b = buckets; - while(b) { struct node *n, *p; int changed = 0; @@ -715,7 +741,7 @@ expire_buckets(int s) } if(changed) - send_cached_ping(s, b); + send_cached_ping(b); b = b->next; } @@ -729,11 +755,11 @@ expire_buckets(int s) transaction id of the protocol packets). */ static struct search * -find_search(unsigned short tid) +find_search(unsigned short tid, int af) { struct search *sr = searches; while(sr) { - if(sr->tid == tid) + if(sr->tid == tid && sr->af == af) return sr; sr = sr->next; } @@ -745,13 +771,19 @@ find_search(unsigned short tid) discard it. */ static int -insert_search_node(unsigned char *id, struct sockaddr_in *sin, +insert_search_node(unsigned char *id, + struct sockaddr *sa, int salen, struct search *sr, int replied, unsigned char *token, int token_len) { struct search_node *n; int i, j; + if(sa->sa_family != sr->af) { + debugf("Attempted to insert node in the wrong family."); + return 0; + } + for(i = 0; i < sr->numnodes; i++) { if(id_cmp(id, sr->nodes[i].id) == 0) { n = &sr->nodes[i]; @@ -777,7 +809,8 @@ insert_search_node(unsigned char *id, struct sockaddr_in *sin, memcpy(n->id, id, 20); found: - n->sin = *sin; + memcpy(&n->ss, sa, salen); + n->sslen = salen; if(replied) { n->replied = 1; @@ -829,7 +862,7 @@ expire_searches(void) /* 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) +search_send_get_peers(struct search *sr, struct search_node *n) { struct node *node; unsigned char tid[4]; @@ -849,22 +882,21 @@ search_send_get_peers(int s, struct search *sr, struct search_node *n) 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, + send_get_peers((struct sockaddr*)&n->ss, n->sslen, tid, 4, sr->id, -1, 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); + node = find_node(n->id, n->ss.ss_family); + if(node) pinged(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) +search_step(struct search *sr, dht_callback *callback, void *closure) { int i, j; int all_done = 1; @@ -902,16 +934,15 @@ search_step(int s, struct search *sr, dht_callback *callback, void *closure) 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), + send_announce_peer((struct sockaddr*)&n->ss, + sizeof(struct sockaddr_storage), 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); + node = find_node(n->id, n->ss.ss_family); + if(node) pinged(node, NULL); } j++; } @@ -927,7 +958,7 @@ search_step(int s, struct search *sr, dht_callback *callback, void *closure) j = 0; for(i = 0; i < sr->numnodes; i++) { - j += search_send_get_peers(s, sr, &sr->nodes[i]); + j += search_send_get_peers(sr, &sr->nodes[i]); if(j >= 3) break; } @@ -937,7 +968,10 @@ search_step(int s, struct search *sr, dht_callback *callback, void *closure) done: sr->done = 1; if(callback) - (*callback)(closure, DHT_EVENT_SEARCH_DONE, sr->id, NULL, 0); + (*callback)(closure, + sr->af == AF_INET ? + DHT_EVENT_SEARCH_DONE : DHT_EVENT_SEARCH_DONE6, + sr->id, NULL, 0); sr->step_time = now.tv_sec; } @@ -981,15 +1015,16 @@ 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); + insert_search_node(n->id, (struct sockaddr*)&n->ss, n->sslen, + 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, +int +dht_search(const unsigned char *id, int port, int af, dht_callback *callback, void *closure) { struct search *sr; @@ -997,7 +1032,7 @@ dht_search(int s, const unsigned char *id, int port, sr = searches; while(sr) { - if(id_cmp(sr->id, id) == 0) + if(sr->af == af && id_cmp(sr->id, id) == 0) break; sr = sr->next; } @@ -1027,6 +1062,7 @@ dht_search(int s, const unsigned char *id, int port, errno = ENOSPC; return -1; } + sr->af = af; sr->tid = search_id++; sr->step_time = 0; memcpy(sr->id, id, 20); @@ -1036,7 +1072,7 @@ dht_search(int s, const unsigned char *id, int port, sr->port = port; - b = find_bucket(id); + b = find_bucket(id, af); insert_search_bucket(b, sr); if(sr->numnodes < SEARCH_NODES) { @@ -1047,9 +1083,9 @@ dht_search(int s, const unsigned char *id, int port, insert_search_bucket(p, sr); } if(sr->numnodes < SEARCH_NODES) - insert_search_bucket(find_bucket(myid), sr); + insert_search_bucket(find_bucket(myid, af), sr); - search_step(s, sr, callback, closure); + search_step(sr, callback, closure); search_time = now.tv_sec; return 1; } @@ -1071,11 +1107,12 @@ find_storage(const unsigned char *id) } static int -storage_store(const unsigned char *id, const unsigned char *ip, - unsigned short port) +storage_store(const unsigned char *id, struct sockaddr *sa) { - int i; + int i, len; struct storage *st = storage; + unsigned char *ip; + short int port; st = find_storage(id); @@ -1087,10 +1124,24 @@ storage_store(const unsigned char *id, const unsigned char *ip, storage = st; } + if(sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)sa; + ip = (unsigned char*)&sin->sin_addr; + len = 4; + port = htons(sin->sin_port); + } else if(sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)sa; + ip = (unsigned char*)&sin6->sin6_addr; + len = 16; + port = htons(sin6->sin6_port); + } + for(i = 0; i < st->numpeers; i++) { - if(st->peers[i].port == port && memcmp(st->peers[i].ip, ip, 4) == 0) + if(st->peers[i].port == port && st->peers[i].len == len && + memcmp(st->peers[i].ip, ip, len) == 0) break; } + if(i < st->numpeers) { /* Already there, only need to refresh */ st->peers[i].time = now.tv_sec; @@ -1112,7 +1163,8 @@ storage_store(const unsigned char *id, const unsigned char *ip, } p = &st->peers[st->numpeers++]; p->time = now.tv_sec; - memcpy(p->ip, ip, 4); + p->len = len; + memcpy(p->ip, ip, len); p->port = port; return 1; } @@ -1155,7 +1207,7 @@ expire_storage(void) /* We've just found out that a node is buggy. */ static void -broken_node(int s, const unsigned char *id, struct sockaddr_in *sin) +broken_node(const unsigned char *id, struct sockaddr *sa, int salen) { int i; @@ -1165,10 +1217,10 @@ broken_node(int s, const unsigned char *id, struct sockaddr_in *sin) struct node *n; struct search *sr; /* Make the node easy to discard. */ - n = find_node(id); + n = find_node(id, sa->sa_family); if(n) { n->pinged = 3; - pinged(s, n, NULL); + pinged(n, NULL); } /* Discard it from any searches in progress. */ sr = searches; @@ -1180,7 +1232,7 @@ broken_node(int s, const unsigned char *id, struct sockaddr_in *sin) } } /* And make sure we don't hear from it again. */ - blacklist[next_blacklisted] = *sin; + memcpy(&blacklist[next_blacklisted], sa, salen); next_blacklisted = (next_blacklisted + 1) % DHT_MAX_BLACKLISTED; } @@ -1205,36 +1257,52 @@ rotate_secrets(void) #endif static void -make_token(const unsigned char *ipv4, unsigned short port, int old, - unsigned char *token_return) +make_token(struct sockaddr *sa, int old, unsigned char *token_return) { + void *ip; + int iplen; + unsigned short port; + + if(sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)sa; + ip = &sin->sin_addr; + iplen = 4; + port = htons(sin->sin_port); + } else if(sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)sa; + ip = &sin6->sin6_addr; + iplen = 16; + port = htons(sin6->sin6_port); + } else { + abort(); + } + dht_hash(token_return, TOKEN_SIZE, old ? oldsecret : secret, sizeof(secret), - ipv4, 4, - (unsigned char*)&port, 2); + ip, iplen, (unsigned char*)&port, 2); } static int -token_match(unsigned char *token, int token_len, - const unsigned char *ipv4, unsigned short port) +token_match(unsigned char *token, int token_len, struct sockaddr *sa) { unsigned char t[TOKEN_SIZE]; if(token_len != TOKEN_SIZE) return 0; - make_token(ipv4, port, 0, t); + make_token(sa, 0, t); if(memcmp(t, token, TOKEN_SIZE) == 0) return 1; - make_token(ipv4, port, 1, t); + make_token(sa, 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, +dht_nodes(int af, 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; + struct bucket *b = af == AF_INET ? buckets : buckets6; + while(b) { struct node *n = b->nodes; while(n) { @@ -1247,7 +1315,7 @@ dht_nodes(int *good_return, int *dubious_return, int *cached_return, } n = n->next; } - if(b->cached.sin_family == AF_INET) + if(b->cached.ss_family > 0) cached++; b = b->next; } @@ -1261,50 +1329,80 @@ dht_nodes(int *good_return, int *dubious_return, int *cached_return, *incoming_return = incoming; return good + dubious; } - + +static void +dump_bucket(FILE *f, struct bucket *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.ss_family ? " (cached)" : ""); + while(n) { + char buf[512]; + unsigned short port; + fprintf(f, " Node "); + print_hex(f, n->id, 20); + if(n->ss.ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)&n->ss; + inet_ntop(AF_INET, &sin->sin_addr, buf, 512); + port = ntohs(sin->sin_port); + } else if(n->ss.ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&n->ss; + inet_ntop(AF_INET6, &sin6->sin6_addr, buf, 512); + port = ntohs(sin6->sin6_port); + } else { + snprintf(buf, 512, "unknown(%d)", n->ss.ss_family); + port = 0; + } + + fprintf(f, " %s:%d ", buf, 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; + } + +} void dht_dump_tables(FILE *f) { int i; - struct bucket *b = buckets; + struct bucket *b; struct storage *st = storage; struct search *sr = searches; fprintf(f, "My id "); print_hex(f, myid, 20); fprintf(f, "\n"); + + b = buckets; + while(b) { + dump_bucket(f, b); + b = b->next; + } + + fprintf(f, "\n"); + + b = buckets6; 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; - } + dump_bucket(f, b); b = b->next; } + while(sr) { - fprintf(f, "\nSearch id "); + fprintf(f, "\nSearch%s id ", sr->af == AF_INET6 ? " (IPv6)" : ""); print_hex(f, sr->id, 20); fprintf(f, " age %d%s\n", (int)(now.tv_sec - sr->step_time), sr->done ? " (done)" : ""); @@ -1319,13 +1417,12 @@ dht_dump_tables(FILE *f) if(n->pinged) fprintf(f, " (%d)", n->pinged); fprintf(f, "%s%s.\n", - find_node(n->id) ? " (known)" : "", - n->replied ? " (replied)" : ""); + find_node(n->id, AF_INET) ? " (known)" : "", + n->replied ? " (replied)" : ""); } sr = sr->next; } - while(st) { fprintf(f, "\nStorage "); print_hex(f, st->id, 20); @@ -1339,17 +1436,17 @@ dht_dump_tables(FILE *f) } st = st->next; } - + fprintf(f, "\n\n"); fflush(f); } int -dht_init(int s, const unsigned char *id, const unsigned char *v) +dht_init(int s, int s6, const unsigned char *id, const unsigned char *v) { int rc; - if(buckets) { + if(dht_socket >= 0 || dht_socket6 >= 0 || buckets || buckets6) { errno = EBUSY; return -1; } @@ -1363,13 +1460,35 @@ dht_init(int s, const unsigned char *id, const unsigned char *v) storage = NULL; - rc = fcntl(s, F_GETFL, 0); - if(rc < 0) - goto fail; + if(s >= 0) { + buckets = calloc(sizeof(struct bucket), 1); + if(buckets == NULL) + return -1; + buckets->af = AF_INET; - rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK)); - if(rc < 0) - goto fail; + rc = fcntl(s, F_GETFL, 0); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK)); + if(rc < 0) + goto fail; + } + + if(s6 >= 0) { + buckets6 = calloc(sizeof(struct bucket), 1); + if(buckets6 == NULL) + return -1; + buckets6->af = AF_INET6; + + rc = fcntl(s6, F_GETFL, 0); + if(rc < 0) + goto fail; + + rc = fcntl(s6, F_SETFL, (rc | O_NONBLOCK)); + if(rc < 0) + goto fail; + } memcpy(myid, id, 20); if(v) { @@ -1383,6 +1502,7 @@ dht_init(int s, const unsigned char *id, const unsigned char *v) gettimeofday(&now, NULL); mybucket_grow_time = now.tv_sec; + mybucket6_grow_time = now.tv_sec; confirm_nodes_time = now.tv_sec + random() % 3; search_id = random() & 0xFFFF; @@ -1398,7 +1518,11 @@ dht_init(int s, const unsigned char *id, const unsigned char *v) if(rc < 0) goto fail; - expire_buckets(s); + dht_socket = s; + dht_socket6 = s6; + + expire_buckets(buckets); + expire_buckets(buckets6); return 1; @@ -1409,8 +1533,13 @@ dht_init(int s, const unsigned char *id, const unsigned char *v) } int -dht_uninit(int s, int dofree) +dht_uninit(int dofree) { + if(dht_socket < 0) { + errno = EINVAL; + return -1; + } + if(!dofree) return 1; @@ -1459,44 +1588,152 @@ leaky_bucket(void) return 1; } +static int +neighbourhood_maintenance(int af) +{ + unsigned char id[20]; + struct bucket *b, *q; + struct node *n; + + memcpy(id, myid, 20); + id[19] = random() % 0xFF; + b = find_bucket(myid, af); + 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) { + int want = dht_socket >= 0 && dht_socket6 >= 0 ? (WANT4 | WANT6) : -1; + 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((struct sockaddr*)&n->ss, n->sslen, + tid, 4, id, want, + n->reply_time >= now.tv_sec - 15); + pinged(n, q); + } + } + return 1; +} + +static int +bucket_maintenance(int af) +{ + struct bucket *b; + + b = af == AF_INET ? buckets : buckets6; + + while(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]; + int want = -1; + + if(dht_socket >= 0 && dht_socket6 >= 0) { + struct bucket *otherbucket; + otherbucket = + find_bucket(id, af == AF_INET ? AF_INET6 : AF_INET); + if(otherbucket && otherbucket->count < 8) + want = WANT4 | WANT6; + } + + debugf("Sending find_node " + "for bucket maintenance.\n"); + make_tid(tid, "fn", 0); + send_find_node((struct sockaddr*)&n->ss, n->sslen, + tid, 4, id, want, + n->reply_time >= now.tv_sec - 15); + pinged(n, q); + /* In order to avoid sending queries back-to-back, + give up for now and reschedule us soon. */ + return 1; + } + } + } + b = b->next; + } + return 0; +} + int -dht_periodic(int s, int available, time_t *tosleep, +dht_periodic(int available, time_t *tosleep, dht_callback *callback, void *closure) { + int i; + gettimeofday(&now, NULL); if(available) { - int rc, i, message; + int rc, message; unsigned char tid[16], id[20], info_hash[20], target[20]; - unsigned char buf[1536], nodes[256], token[128]; + unsigned char buf[1536], nodes[256], nodes6[1024], token[128]; int tid_len = 16, token_len = 128; - int nodes_len = 256; + int nodes_len = 256, nodes6_len = 1024; unsigned short port; - unsigned char values[2048]; - int values_len = 2048; - struct sockaddr_in source; - socklen_t source_len = sizeof(struct sockaddr_in); + unsigned char values[2048], values6[2048]; + int values_len = 2048, values6_len = 2048; + int want, want4, want6; + struct sockaddr_storage source_storage; + struct sockaddr *source = (struct sockaddr*)&source_storage; + socklen_t sourcelen = sizeof(source_storage); 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; + rc = -1; + if(dht_socket >= 0) { + rc = recvfrom(dht_socket, buf, 1536, 0, source, &sourcelen); + if(rc < 0 && errno != EAGAIN) { + return rc; + } } - - if(source_len != sizeof(struct sockaddr_in)) { - /* Hmm... somebody gave us an IPv6 socket. */ - errno = EINVAL; - return -1; + if(dht_socket6 >= 0 && rc < 0) { + rc = recvfrom(dht_socket6, buf, 1536, 0, + (struct sockaddr*)source, &sourcelen); + if(rc < 0 && errno != EAGAIN) { + return rc; + } } + if(rc < 0 || sourcelen > sizeof(struct sockaddr_storage)) + goto dontread; + 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) { + if(memcmp(&blacklist[i], source, sourcelen) == 0) { debugf("Received packet from blacklisted node.\n"); goto dontread; } @@ -1515,7 +1752,9 @@ dht_periodic(int s, int available, time_t *tosleep, message = parse_message(buf, rc, tid, &tid_len, id, info_hash, target, &port, token, &token_len, - nodes, &nodes_len, values, &values_len); + nodes, &nodes_len, nodes6, &nodes6_len, + values, &values_len, values6, &values6_len, + &want); if(id_cmp(id, zeroes) == 0) { debugf("Message with no id: "); debug_printable(buf, rc); @@ -1536,6 +1775,14 @@ dht_periodic(int s, int available, time_t *tosleep, } } + if(want > 0) { + want4 = (want & WANT4); + want6 = (want & WANT6); + } else { + want4 = source->sa_family == AF_INET; + want6 = source->sa_family == AF_INET6; + } + switch(message) { case REPLY: if(tid_len != 4) { @@ -1545,31 +1792,31 @@ dht_periodic(int s, int available, time_t *tosleep, /* 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); + broken_node(id, source, sourcelen); goto dontread; } if(tid_match(tid, "pn", NULL)) { debugf("Pong!\n"); - new_node(s, id, &source, 2); + new_node(id, source, sourcelen, 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); + sr = find_search(ttid, source->sa_family); } - debugf("Nodes found (%d)%s!\n", nodes_len / 26, + debugf("Nodes found (%d+%d)%s!\n", nodes_len/26, nodes6_len/38, gp ? " for get_peers" : ""); - if(nodes_len % 26 != 0) { + if(nodes_len % 26 != 0 || nodes6_len % 38 != 0) { debugf("Unexpected length for node info!\n"); - broken_node(s, id, &source); + broken_node(id, source, sourcelen); } else if(gp && sr == NULL) { debugf("Unknown search!\n"); - new_node(s, id, &source, 1); + new_node(id, source, sourcelen, 1); } else { int i; - new_node(s, id, &source, 2); + new_node(id, source, sourcelen, 2); for(i = 0; i < nodes_len / 26; i++) { unsigned char *ni = nodes + i * 26; struct sockaddr_in sin; @@ -1579,38 +1826,64 @@ dht_periodic(int s, int available, time_t *tosleep, 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); + new_node(ni, (struct sockaddr*)&sin, sizeof(sin), 0); + if(sr) { + insert_search_node(ni, + (struct sockaddr*)&sin, + sizeof(sin), + sr, 0, NULL, 0); + } + } + for(i = 0; i < nodes6_len / 38; i++) { + unsigned char *ni = nodes6 + i * 38; + struct sockaddr_in6 sin6; + if(id_cmp(ni, myid) == 0) + continue; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, ni + 20, 16); + memcpy(&sin6.sin6_port, ni + 36, 2); + new_node(ni, (struct sockaddr*)&sin6, sizeof(sin6), 0); if(sr) { - insert_search_node(ni, &sin, sr, 0, NULL, 0); + insert_search_node(ni, + (struct sockaddr*)&sin6, + sizeof(sin6), + 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); + search_send_get_peers(sr, NULL); } if(sr) { - insert_search_node(id, &source, sr, + insert_search_node(id, source, sourcelen, sr, 1, token, token_len); - if(values_len > 0) { - debugf("Got values (%d)!\n", values_len / 6); + if(values_len > 0 || values6_len > 0) { + debugf("Got values (%d+%d)!\n", + values_len / 6, values6_len / 18); if(callback) { - (*callback)(closure, DHT_EVENT_VALUES, - sr->id, (void*)values, values_len); + if(values_len > 0) + (*callback)(closure, DHT_EVENT_VALUES, sr->id, + (void*)values, values_len); + + if(values6_len > 0) + (*callback)(closure, DHT_EVENT_VALUES6, sr->id, + (void*)values6, values6_len); } } } } else if(tid_match(tid, "ap", &ttid)) { struct search *sr; debugf("Got reply to announce_peer.\n"); - sr = find_search(ttid); + sr = find_search(ttid, source->sa_family); if(!sr) { debugf("Unknown search!\n"); - new_node(s, id, &source, 1); + new_node(id, source, sourcelen, 1); } else { int i; - new_node(s, id, &source, 2); + new_node(id, source, sourcelen, 2); for(i = 0; i < sr->numnodes; i++) if(id_cmp(sr->nodes[i].id, id) == 0) { sr->nodes[i].request_time = 0; @@ -1620,7 +1893,7 @@ dht_periodic(int s, int available, time_t *tosleep, break; } /* See comment for gp above. */ - search_send_get_peers(s, sr, NULL); + search_send_get_peers(sr, NULL); } } else { debugf("Unexpected reply: "); @@ -1630,69 +1903,52 @@ dht_periodic(int s, int available, time_t *tosleep, break; case PING: debugf("Ping (%d)!\n", tid_len); - new_node(s, id, &source, 1); + new_node(id, source, sourcelen, 1); debugf("Sending pong.\n"); - send_pong(s, (struct sockaddr*)&source, sizeof(source), - tid, tid_len); + send_pong(source, sourcelen, 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); + new_node(id, source, sourcelen, 1); + debugf("Sending closest nodes (%d).\n", want); + send_closest_nodes(source, sourcelen, + tid, tid_len, target, want, + 0, NULL, NULL, 0); break; case GET_PEERS: debugf("Get_peers!\n"); - new_node(s, id, &source, 1); + new_node(id, source, sourcelen, 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); + make_token(source, 0, token); + debugf("Sending found%s peers.\n", + source->sa_family == AF_INET6 ? " IPv6" : ""); + send_closest_nodes(source, sourcelen, + tid, tid_len, + info_hash, want, + source->sa_family, st, + 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); + send_closest_nodes(source, sizeof(source), + tid, tid_len, info_hash, want, + 0, NULL, token, TOKEN_SIZE); } } break; case ANNOUNCE_PEER: debugf("Announce peer!\n"); - new_node(s, id, &source, 1); + new_node(id, source, sourcelen, 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))) { + if(!token_match(token, token_len, source)) { debugf("Incorrect token for announce_peer.\n"); break; } @@ -1700,20 +1956,19 @@ dht_periodic(int s, int available, time_t *tosleep, debugf("Announce_peer with forbidden port %d.\n", port); break; } - storage_store(info_hash, - (unsigned char*)&source.sin_addr, port); + storage_store(info_hash, source); debugf("Sending peer announced.\n"); - send_peer_announced(s, (struct sockaddr*)&source, - sizeof(source), tid, tid_len); + send_peer_announced(source, sourcelen, tid, tid_len); + } } - } dontread: if(now.tv_sec >= rotate_secrets_time) rotate_secrets(); if(now.tv_sec >= expire_stuff_time) { - expire_buckets(s); + expire_buckets(buckets); + expire_buckets(buckets6); expire_storage(); expire_searches(); } @@ -1723,11 +1978,11 @@ dht_periodic(int s, int available, time_t *tosleep, sr = searches; while(sr) { if(!sr->done && sr->step_time + 5 <= now.tv_sec) { - search_step(s, sr, callback, closure); + search_step(sr, callback, closure); } sr = sr->next; } - + search_time = 0; sr = searches; @@ -1742,92 +1997,16 @@ dht_periodic(int s, int available, time_t *tosleep, } 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; - } + soon |= bucket_maintenance(AF_INET); + soon |= bucket_maintenance(AF_INET6); - 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; + if(!soon) { + if(mybucket_grow_time >= now.tv_sec - 150) + soon |= neighbourhood_maintenance(AF_INET); + if(mybucket6_grow_time >= now.tv_sec - 150) + soon |= neighbourhood_maintenance(AF_INET6); } /* In order to maintain all buckets' age within 900 seconds, worst @@ -1851,14 +2030,15 @@ dht_periodic(int s, int available, time_t *tosleep, else if(*tosleep > search_time - now.tv_sec) *tosleep = search_time - now.tv_sec; } - - return find_bucket(myid)->count > 2; + + return find_bucket(myid, AF_INET)->count > 2; } int -dht_get_nodes(struct sockaddr_in *sins, int num) +dht_get_nodes(struct sockaddr_in *sin, int *num, + struct sockaddr_in6 *sin6, int *num6) { - int i; + int i, j; struct bucket *b; struct node *n; @@ -1866,23 +2046,23 @@ dht_get_nodes(struct sockaddr_in *sins, int num) /* For restoring to work without discarding too many nodes, the list must start with the contents of our bucket. */ - b = find_bucket(myid); + b = find_bucket(myid, AF_INET); n = b->nodes; - while(n && i < num) { + while(n && i < *num) { if(node_good(n)) { - sins[i] = n->sin; + sin[i] = *(struct sockaddr_in*)&n->ss; i++; } n = n->next; } b = buckets; - while(b && i < num) { + while(b && i < *num) { if(!in_bucket(myid, b)) { n = b->nodes; - while(n && i < num) { + while(n && i < *num) { if(node_good(n)) { - sins[i] = n->sin; + sin[i] = *(struct sockaddr_in*)&n->ss; i++; } n = n->next; @@ -1890,25 +2070,61 @@ dht_get_nodes(struct sockaddr_in *sins, int num) } b = b->next; } - return i; + + j = 0; + + b = find_bucket(myid, AF_INET6); + n = b->nodes; + while(n && j < *num6) { + if(node_good(n)) { + sin[j] = *(struct sockaddr_in*)&n->ss; + j++; + } + n = n->next; + } + + b = buckets6; + while(b && j < *num6) { + if(!in_bucket(myid, b)) { + n = b->nodes; + while(n && j < *num6) { + if(node_good(n)) { + sin6[j] = *(struct sockaddr_in6*)&n->ss; + j++; + } + n = n->next; + } + } + b = b->next; + } + + *num = i; + *num6 = j; + return i + j; } int -dht_insert_node(int s, const unsigned char *id, struct sockaddr_in *sin) +dht_insert_node(const unsigned char *id, struct sockaddr *sa, int salen) { struct node *n; - n = new_node(s, id, sin, 0); + + if(sa->sa_family != AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + + n = new_node(id, (struct sockaddr*)sa, salen, 0); return !!n; } int -dht_ping_node(int s, struct sockaddr_in *sin) +dht_ping_node(struct sockaddr *sa, int salen) { 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); + return send_ping(sa, salen, tid, 4); } /* We could use a proper bencoding printer and parser, but the format of @@ -1931,8 +2147,32 @@ dht_ping_node(int s, struct sockaddr_in *sin) COPY(buf, offset, my_v, sizeof(my_v), size); \ } +static int +dht_send(const void *buf, size_t len, int flags, + const struct sockaddr *sa, int salen) +{ + int s; + + if(salen == 0) + abort(); + + if(sa->sa_family == AF_INET) + s = dht_socket; + else if(sa->sa_family == AF_INET6) + s = dht_socket6; + else + s = -1; + + if(s < 0) { + errno = EAFNOSUPPORT; + return -1; + } + + return sendto(s, buf, len, flags, sa, salen); +} + int -send_ping(int s, struct sockaddr *sa, int salen, +send_ping(struct sockaddr *sa, int salen, const unsigned char *tid, int tid_len) { char buf[512]; @@ -1944,7 +2184,7 @@ send_ping(int s, struct sockaddr *sa, int salen, 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); + return dht_send(buf, i, 0, sa, salen); fail: errno = ENOSPC; @@ -1952,7 +2192,7 @@ send_ping(int s, struct sockaddr *sa, int salen, } int -send_pong(int s, struct sockaddr *sa, int salen, +send_pong(struct sockaddr *sa, int salen, const unsigned char *tid, int tid_len) { char buf[512]; @@ -1963,7 +2203,7 @@ send_pong(int s, struct sockaddr *sa, int salen, 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); + return dht_send(buf, i, 0, sa, salen); fail: errno = ENOSPC; @@ -1971,9 +2211,9 @@ send_pong(int s, struct sockaddr *sa, int salen, } int -send_find_node(int s, struct sockaddr *sa, int salen, +send_find_node(struct sockaddr *sa, int salen, const unsigned char *tid, int tid_len, - const unsigned char *target, int confirm) + const unsigned char *target, int want, int confirm) { char buf[512]; int i = 0, rc; @@ -1981,12 +2221,18 @@ send_find_node(int s, struct sockaddr *sa, int salen, COPY(buf, i, myid, 20, 512); rc = snprintf(buf + i, 512 - i, "6:target20:"); INC(i, rc, 512); COPY(buf, i, target, 20, 512); + if(want > 0) { + rc = snprintf(buf + i, 512 - i, "4:wantl%s%se", + (want & WANT4) ? "2:n4" : "", + (want & WANT6) ? "2:n6" : ""); + INC(i, rc, 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); + return dht_send(buf, i, confirm ? MSG_CONFIRM : 0, sa, salen); fail: errno = ENOSPC; @@ -1994,15 +2240,16 @@ send_find_node(int s, struct sockaddr *sa, int salen, } int -send_nodes_peers(int s, struct sockaddr *sa, int salen, +send_nodes_peers(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 *nodes6, int nodes6_len, + int af, struct storage *st, const unsigned char *token, int token_len) { char buf[2048]; - int i = 0, rc, j; + int i = 0, rc, j0, j, k, len; + rc = snprintf(buf + i, 2048 - i, "d1:rd2:id20:"); INC(i, rc, 2048); COPY(buf, i, myid, 20, 2048); if(token_len > 0) { @@ -2015,24 +2262,41 @@ send_nodes_peers(int s, struct sockaddr *sa, int salen, 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); + if(nodes6_len > 0) { + rc = snprintf(buf + i, 2048 - i, "6:nodes6%d:", nodes6_len); + INC(i, rc, 2048); + COPY(buf, i, nodes6, nodes6_len, 2048); + } + + if(st && st->numpeers > 0) { + /* We treat the storage as a circular list, and serve a randomly + chosen slice. In order to make sure we fit within 1024 octets, + we limit ourselves to 50 peers. */ + + len = af == AF_INET ? 4 : 16; + j0 = random() % st->numpeers; + j = j0; + k = 0; + + do { + if(st->peers[j].len == len) { + unsigned short swapped; + swapped = htons(st->peers[j].port); + rc = snprintf(buf + i, 2048 - i, "%d:", len); INC(i, rc, 2048); + COPY(buf, i, st->peers[j].ip, len, 2048); + COPY(buf, i, &swapped, 2, 2048); + k++; + } + j = (j + 1) % st->numpeers; + } while(j != j0 && k < 50); } + 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); + return dht_send(buf, i, 0, sa, salen); fail: errno = ENOSPC; @@ -2043,11 +2307,19 @@ static int insert_closest_node(unsigned char *nodes, int numnodes, const unsigned char *id, struct node *n) { - int i; + int i, size; + + if(n->ss.ss_family == AF_INET) + size = 26; + else if(n->ss.ss_family == AF_INET6) + size = 38; + else + abort(); + for(i = 0; i< numnodes; i++) { - if(id_cmp(nodes + 26 * i, id) == 0) + if(id_cmp(nodes + size * i, id) == 0) return numnodes; - if(xorcmp(n->id, nodes + 26 * i, id) < 0) + if(xorcmp(n->id, nodes + size * i, id) < 0) break; } @@ -2058,11 +2330,22 @@ insert_closest_node(unsigned char *nodes, int numnodes, 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); + memmove(nodes + size * (i + 1), nodes + size * i, + size * (numnodes - i - 1)); + + if(n->ss.ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)&n->ss; + memcpy(nodes + size * i, n->id, 20); + memcpy(nodes + size * i + 20, &sin->sin_addr, 4); + memcpy(nodes + size * i + 24, &sin->sin_port, 2); + } else if(n->ss.ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&n->ss; + memcpy(nodes + size * i, n->id, 20); + memcpy(nodes + size * i + 20, &sin6->sin6_addr, 16); + memcpy(nodes + size * i + 36, &sin6->sin6_port, 2); + } else { + abort(); + } return numnodes; } @@ -2081,35 +2364,50 @@ buffer_closest_nodes(unsigned char *nodes, int numnodes, } int -send_closest_nodes(int s, struct sockaddr *sa, int salen, +send_closest_nodes(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 *id, int want, + int af, struct storage *st, const unsigned char *token, int token_len) { unsigned char nodes[8 * 26]; - int numnodes = 0; + unsigned char nodes6[8 * 38]; + int numnodes = 0, numnodes6 = 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) + if(want < 0) + want = sa->sa_family == AF_INET ? WANT4 : WANT6; + + if((want & WANT4)) { + b = find_bucket(id, AF_INET); 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, + if((want & WANT6)) { + b = find_bucket(id, AF_INET6); + numnodes6 = buffer_closest_nodes(nodes6, numnodes6, id, b); + if(b->next) + numnodes6 = buffer_closest_nodes(nodes6, numnodes6, id, b->next); + b = previous_bucket(b); + if(b) + numnodes6 = buffer_closest_nodes(nodes6, numnodes6, id, b); + } + + return send_nodes_peers(sa, salen, tid, tid_len, nodes, numnodes * 26, - peers1, numpeers1, peers2, numpeers2, - token, token_len); + nodes6, numnodes6 * 38, + af, st, token, token_len); } int -send_get_peers(int s, struct sockaddr *sa, int salen, +send_get_peers(struct sockaddr *sa, int salen, unsigned char *tid, int tid_len, unsigned char *infohash, - int confirm) + int want, int confirm) { char buf[512]; int i = 0, rc; @@ -2118,12 +2416,18 @@ send_get_peers(int s, struct sockaddr *sa, int salen, 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); + if(want > 0) { + rc = snprintf(buf + i, 512 - i, "4:wantl%s%se", + (want & WANT4) ? "2:n4" : "", + (want & WANT6) ? "2:n6" : ""); + INC(i, rc, 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); + return dht_send(buf, i, confirm ? MSG_CONFIRM : 0, sa, salen); fail: errno = ENOSPC; @@ -2131,7 +2435,7 @@ send_get_peers(int s, struct sockaddr *sa, int salen, } int -send_announce_peer(int s, struct sockaddr *sa, int salen, +send_announce_peer(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) @@ -2153,7 +2457,7 @@ send_announce_peer(int s, struct sockaddr *sa, int salen, 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); + return dht_send(buf, i, confirm ? 0 : MSG_CONFIRM, sa, salen); fail: errno = ENOSPC; @@ -2161,7 +2465,7 @@ send_announce_peer(int s, struct sockaddr *sa, int salen, } int -send_peer_announced(int s, struct sockaddr *sa, int salen, +send_peer_announced(struct sockaddr *sa, int salen, unsigned char *tid, int tid_len) { char buf[512]; @@ -2174,7 +2478,7 @@ send_peer_announced(int s, struct sockaddr *sa, int salen, 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); + return dht_send(buf, i, 0, sa, salen); fail: errno = ENOSPC; @@ -2214,7 +2518,10 @@ parse_message(const unsigned char *buf, int buflen, 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) + unsigned char *nodes6_return, int *nodes6_len, + unsigned char *values_return, int *values_len, + unsigned char *values6_return, int *values6_len, + int *want_return) { const unsigned char *p; @@ -2290,8 +2597,8 @@ parse_message(const unsigned char *buf, int buflen, } else *token_len = 0; } - - if(nodes_return) { + + if(nodes_len) { p = memmem(buf, buflen, "5:nodes", 7); if(p) { long l; @@ -2307,42 +2614,89 @@ parse_message(const unsigned char *buf, int buflen, *nodes_len = 0; } - if(values_return) { + if(nodes6_len) { + p = memmem(buf, buflen, "6:nodes6", 8); + if(p) { + long l; + char *q; + l = strtol((char*)p + 8, &q, 10); + if(q && *q == ':' && l > 0 && l < *nodes6_len) { + CHECK(q + 1, l); + memcpy(nodes6_return, q + 1, l); + *nodes6_len = l; + } else + *nodes6_len = 0; + } else + *nodes6_len = 0; + } + + if(values_len || values6_len) { p = memmem(buf, buflen, "6:valuesl", 9); if(p) { int i = p - buf + 9; - int j = 0; + int j = 0, j6 = 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; + if(l == 6) { + if(j + l > *values_len) + continue; + i = q + 1 + l - (char*)buf; + memcpy((char*)values_return + j, q + 1, l); + j += l; + } else if(l == 18) { + if(j6 + l > *values6_len) + continue; + i = q + 1 + l - (char*)buf; + memcpy((char*)values6_return + j6, q + 1, l); + j6 += l; + } else { + debugf("Received weird value -- %d bytes.\n", (int)l); } - 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; + if(values_len) + *values_len = j; + if(values6_len) + *values6_len = j6; } else { *values_len = 0; + *values6_len = 0; + } + } + + if(want_return) { + p = memmem(buf, buflen, "4:wantl", 7); + if(p) { + int i = p - buf + 7; + *want_return = 0; + while(buf[i] > '0' && buf[i] <= '9' && buf[i + 1] == ':' && + i + 2 + buf[i] - '0' < buflen) { + CHECK(buf + i + 2, buf[i] - '0'); + if(buf[i] == '2' && memcmp(buf + i + 2, "n4", 2) == 0) + *want_return |= WANT4; + else if(buf[i] == '2' && memcmp(buf + i + 2, "n6", 2) == 0) + *want_return |= WANT6; + else + debugf("eek... unexpected want flag (%c)\n", buf[i]); + i += 2 + buf[i] - '0'; + } + if(i >= buflen || buf[i] != 'e') + debugf("eek... unexpected end for want.\n"); + } else { + *want_return = -1; } } #undef CHECK - + if(memmem(buf, buflen, "1:y1:r", 6)) return REPLY; if(!memmem(buf, buflen, "1:y1:q", 6)) diff --git a/third-party/dht/dht.h b/third-party/dht/dht.h index d9cce19..a673b23 100644 --- a/third-party/dht/dht.h +++ b/third-party/dht/dht.h @@ -27,22 +27,26 @@ dht_callback(void *closure, int event, #define DHT_EVENT_NONE 0 #define DHT_EVENT_VALUES 1 -#define DHT_EVENT_SEARCH_DONE 2 +#define DHT_EVENT_VALUES6 2 +#define DHT_EVENT_SEARCH_DONE 3 +#define DHT_EVENT_SEARCH_DONE6 4 extern FILE *dht_debug; -int dht_init(int s, const unsigned char *id, const unsigned char *v); -int dht_insert_node(int s, const unsigned char *id, struct sockaddr_in *sin); -int dht_ping_node(int s, struct sockaddr_in *sin); -int dht_periodic(int s, int available, time_t *tosleep, +int dht_init(int s, int s6, const unsigned char *id, const unsigned char *v); +int dht_insert_node(const unsigned char *id, struct sockaddr *sa, int salen); +int dht_ping_node(struct sockaddr *sa, int salen); +int dht_periodic(int available, time_t *tosleep, dht_callback *callback, void *closure); -int dht_search(int s, const unsigned char *id, int port, +int dht_search(const unsigned char *id, int port, int af, dht_callback *callback, void *closure); -int dht_nodes(int *good_return, int *dubious_return, int *cached_return, +int dht_nodes(int af, + int *good_return, int *dubious_return, int *cached_return, int *incoming_return); void dht_dump_tables(FILE *f); -int dht_get_nodes(struct sockaddr_in *sins, int num); -int dht_uninit(int s, int dofree); +int dht_get_nodes(struct sockaddr_in *sin, int *num, + struct sockaddr_in6 *sin6, int *num6); +int dht_uninit(int dofree); /* This must be provided by the user. */ void dht_hash(void *hash_return, int hash_size, -- 1.6.5.2 From ea649ae7b2331aa8ce4cd166bcb86fb2d38906a2 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Fri, 13 Nov 2009 16:14:15 +0100 Subject: [PATCH 2/2] IPv6 support for the DHT. --- libtransmission/peer-msgs.c | 6 +- libtransmission/torrent.h | 1 + libtransmission/tr-dht.c | 229 +++++++++++++++++++++++++++++-------------- libtransmission/tr-dht.h | 2 +- 4 files changed, 162 insertions(+), 76 deletions(-) diff --git a/libtransmission/peer-msgs.c b/libtransmission/peer-msgs.c index cb06bad..a280e7c 100644 --- a/libtransmission/peer-msgs.c +++ b/libtransmission/peer-msgs.c @@ -2127,10 +2127,10 @@ tr_peerMsgsNew( struct tr_torrent * torrent, sendLtepHandshake( m ); if(tr_peerIoSupportsDHT(peer->io)) { - /* We don't have an IPv6 DHT yet. - * According to BEP-32, we can't send PORT over IPv6. */ + /* According to BEP-32, we can't send PORT over IPv6 only when the + IPv6 DHT is running. */ const struct tr_address *addr = tr_peerIoGetAddress( peer->io, NULL ); - if( addr->type == TR_AF_INET ) { + if( addr->type == TR_AF_INET || tr_globalIPv6() ) { protocolSendPort( m, tr_dhtPort( torrent->session ) ); } } diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index f3e2256..4b32849 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -187,6 +187,7 @@ struct tr_torrent time_t dhtAnnounceAt; tr_bool dhtAnnounceInProgress; + tr_bool dhtAnnounce6InProgress; uint64_t downloadedCur; uint64_t downloadedPrev; diff --git a/libtransmission/tr-dht.c b/libtransmission/tr-dht.c index 46be697..4acafc7 100644 --- a/libtransmission/tr-dht.c +++ b/libtransmission/tr-dht.c @@ -79,7 +79,7 @@ THE SOFTWARE. #else -static int dht_socket; +static int dht_socket = -1, dht6_socket = -1; static struct event dht_event; static tr_port dht_port; static unsigned char myid[20]; @@ -116,11 +116,13 @@ dht_bootstrap(void *closure) port = ntohs(port); /* There's no race here -- if we uninit between the test and the AddNode, the AddNode will be ignored. */ - status = tr_dhtStatus(cl->session, NULL); + status = tr_dhtStatus(cl->session, AF_INET, NULL); if(status == TR_DHT_STOPPED || status >= TR_DHT_FIREWALLED) break; tr_dhtAddNode(cl->session, &addr, port, 1); - tr_timevalSet( &tv, 2 + tr_cryptoWeakRandInt( 5 ), tr_cryptoWeakRandInt( 1000000 ) ); + tr_timevalSet( &tv, + 2 + tr_cryptoWeakRandInt( 5 ), + tr_cryptoWeakRandInt( 1000000 ) ); select( 0, NULL, NULL, NULL, &tv ); } tr_free( cl->nodes ); @@ -132,14 +134,16 @@ int tr_dhtInit(tr_session *ss, tr_address * tr_addr) { struct sockaddr_in sin; + struct sockaddr_in6 sin6; tr_benc benc; int rc; tr_bool have_id = FALSE; char * dat_file; - uint8_t * nodes = NULL; + uint8_t * nodes = NULL, * nodes6 = NULL; const uint8_t * raw; - size_t len; + size_t len, len6; char v[5]; + const unsigned char *ipv6; if( session ) /* already initialized */ return -1; @@ -156,12 +160,29 @@ tr_dhtInit(tr_session *ss, tr_address * tr_addr) memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; - memcpy(&(sin.sin_addr), &(tr_addr->addr.addr4), sizeof (struct in_addr)); + memcpy(&sin.sin_addr, &tr_addr->addr.addr4, sizeof (struct in_addr)); sin.sin_port = htons(dht_port); rc = bind(dht_socket, (struct sockaddr*)&sin, sizeof(sin)); if(rc < 0) goto fail; + ipv6 = tr_globalIPv6(); + if(ipv6) { + dht6_socket = socket(PF_INET6, SOCK_DGRAM, 0); + if(dht6_socket < 0) + goto fail; + + memset(&sin6, 0, sizeof(sin6)); + sin.sin_family = AF_INET6; + /* BEP-32 has a rather nice explanation of why we need to bind, + if I may say so myself. */ + memcpy(&sin6.sin6_addr, ipv6, 16); + sin6.sin6_port = htons(dht_port); + rc = bind(dht6_socket, (struct sockaddr*)&sin6, sizeof(sin6)); + if(rc < 0) + goto fail; + } + if( getenv( "TR_DHT_VERBOSE" ) != NULL ) dht_debug = stderr; @@ -169,11 +190,19 @@ tr_dhtInit(tr_session *ss, tr_address * tr_addr) rc = tr_bencLoadFile( &benc, TR_FMT_BENC, dat_file ); tr_free( dat_file ); if(rc == 0) { - if(( have_id = tr_bencDictFindRaw( &benc, "id", &raw, &len ) && len==20 )) + have_id = tr_bencDictFindRaw(&benc, "id", &raw, &len); + if( have_id && len==20 ) memcpy( myid, raw, len ); if( tr_bencDictFindRaw( &benc, "nodes", &raw, &len ) && !(len%6) ) { nodes = tr_memdup( raw, len ); - tr_ninf( "DHT", "Bootstrapping from %d old nodes", (int)(len/6) ); + tr_ninf( "DHT", "Bootstrapping from %d nodes", (int)(len/6) ); + } + if( dht6_socket > 0 && + tr_bencDictFindRaw( &benc, "nodes6", &raw, &len6 ) && + !(len6%18) ) { + nodes6 = tr_memdup( raw, len6 ); + tr_ninf( "DHT", "Bootstrapping from %d IPv6 nodes", + (int)(len6/18) ); } tr_bencFree( &benc ); } @@ -191,7 +220,7 @@ tr_dhtInit(tr_session *ss, tr_address * tr_addr) v[1] = 'R'; v[2] = (SVN_REVISION_NUM >> 8) & 0xFF; v[3] = SVN_REVISION_NUM & 0xFF; - rc = dht_init( dht_socket, myid, (const unsigned char*)v ); + rc = dht_init( dht_socket, dht6_socket, myid, (const unsigned char*)v ); if(rc < 0) goto fail; @@ -206,6 +235,7 @@ tr_dhtInit(tr_session *ss, tr_address * tr_addr) } event_set( &dht_event, dht_socket, EV_READ, event_callback, NULL ); + event_set( &dht_event, dht6_socket, EV_READ, event_callback, NULL ); tr_timerAdd( &dht_event, 0, tr_cryptoWeakRandInt( 1000000 ) ); tr_ndbg( "DHT", "DHT initialized" ); @@ -216,7 +246,9 @@ tr_dhtInit(tr_session *ss, tr_address * tr_addr) { const int save = errno; close(dht_socket); - dht_socket = -1; + if( dht6_socket >= 0 ) + close(dht6_socket); + dht_socket = dht6_socket = -1; session = NULL; tr_ndbg( "DHT", "DHT initialization failed (errno = %d)", save ); errno = save; @@ -237,34 +269,47 @@ tr_dhtUninit(tr_session *ss) /* Since we only save known good nodes, avoid erasing older data if we don't know enough nodes. */ - if(tr_dhtStatus(ss, NULL) < TR_DHT_FIREWALLED) + if(tr_dhtStatus(ss, AF_INET, NULL) < TR_DHT_FIREWALLED) tr_ninf( "DHT", "Not saving nodes, DHT not ready" ); else { tr_benc benc; struct sockaddr_in sins[300]; - char compact[300 * 6]; + struct sockaddr_in6 sins6[300]; + char compact[300 * 6], compact6[300 * 18]; char *dat_file; - int i; - int n = dht_get_nodes(sins, 300); - int j = 0; + int i, j, num, num6; + int n = dht_get_nodes(sins, &num, sins6, &num6); - tr_ninf( "DHT", "Saving %d nodes", n ); - for( i=0; i 0) + tr_bencDictAddRaw( &benc, "nodes", compact, num * 6 ); + if(num6 > 0) + tr_bencDictAddRaw( &benc, "nodes6", compact6, num6 * 18 ); dat_file = tr_buildPath( ss->configDir, "dht.dat", NULL ); tr_bencToFile( &benc, TR_FMT_BENC, dat_file ); tr_bencFree( &benc ); tr_free( dat_file ); } - dht_uninit( dht_socket, 0 ); + dht_uninit( 1 ); tr_netCloseSocket( dht_socket ); + if(dht6_socket > 0) + tr_netCloseSocket( dht6_socket ); tr_ndbg("DHT", "Done uninitializing DHT"); @@ -279,46 +324,47 @@ tr_dhtEnabled( const tr_session * ss ) struct getstatus_closure { + int af; sig_atomic_t status; sig_atomic_t count; }; static void -getstatus( void * closure ) +getstatus( void * cl ) { - struct getstatus_closure * ret = closure; + struct getstatus_closure * closure = cl; int good, dubious, incoming; - dht_nodes( &good, &dubious, NULL, &incoming ); + dht_nodes( closure->af, &good, &dubious, NULL, &incoming ); - ret->count = good + dubious; + closure->count = good + dubious; if( good < 4 || good + dubious <= 8 ) - ret->status = TR_DHT_BROKEN; + closure->status = TR_DHT_BROKEN; else if( good < 40 ) - ret->status = TR_DHT_POOR; + closure->status = TR_DHT_POOR; else if( incoming < 8 ) - ret->status = TR_DHT_FIREWALLED; + closure->status = TR_DHT_FIREWALLED; else - ret->status = TR_DHT_GOOD; + closure->status = TR_DHT_GOOD; } int -tr_dhtStatus( tr_session * ss, int * nodes_return ) +tr_dhtStatus( tr_session * ss, int af, int * nodes_return ) { - struct getstatus_closure ret = { -1, - 1 }; + struct getstatus_closure closure = { af, -1, -1 }; if( !tr_dhtEnabled( ss ) ) return TR_DHT_STOPPED; - tr_runInEventThread( ss, getstatus, &ret ); - while( ret.status < 0 ) + tr_runInEventThread( ss, getstatus, &closure ); + while( closure.status < 0 ) tr_wait( 10 /*msec*/ ); if( nodes_return ) - *nodes_return = ret.count; + *nodes_return = closure.count; - return ret.status; + return closure.status; } tr_port @@ -333,28 +379,38 @@ tr_dhtAddNode( tr_session * ss, tr_port port, tr_bool bootstrap ) { - struct sockaddr_in sin; + int af = address->type == TR_AF_INET ? AF_INET : AF_INET6; if( !tr_dhtEnabled( ss ) ) return 0; - if( address->type != TR_AF_INET ) - return 0; - /* Since we don't want to abuse our bootstrap nodes, * we don't ping them if the DHT is in a good state. */ + if(bootstrap) { - if(tr_dhtStatus(ss, NULL) >= TR_DHT_FIREWALLED) + if(tr_dhtStatus(ss, af, NULL) >= TR_DHT_FIREWALLED) return 0; } - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - memcpy(&sin.sin_addr, &address->addr.addr4, 4); - sin.sin_port = htons(port); - dht_ping_node(dht_socket, &sin); - - return 1; + if( address->type == TR_AF_INET ) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + memcpy(&sin.sin_addr, &address->addr.addr4, 4); + sin.sin_port = htons(port); + dht_ping_node((struct sockaddr*)&sin, sizeof(sin)); + return 1; + } else if( address->type == TR_AF_INET6 ) { + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &address->addr.addr6, 16); + sin6.sin6_port = htons(port); + dht_ping_node((struct sockaddr*)&sin6, sizeof(sin6)); + return 1; + } + + return 0; } const char * @@ -374,28 +430,37 @@ static void callback( void *ignore UNUSED, int event, unsigned char *info_hash, void *data, size_t data_len ) { - if( event == DHT_EVENT_VALUES ) - { + if( event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6 ) { tr_torrent *tor; tr_globalLock( session ); tor = tr_torrentFindFromHash( session, info_hash ); if( tor && tr_torrentAllowsDHT( tor )) { size_t i, n; - tr_pex * pex = tr_peerMgrCompactToPex(data, data_len, NULL, 0, &n); + tr_pex * pex; + if( event == DHT_EVENT_VALUES ) + pex = tr_peerMgrCompactToPex(data, data_len, NULL, 0, &n); + else + pex = tr_peerMgrCompact6ToPex(data, data_len, NULL, 0, &n); for( i=0; idhtAnnounceInProgress = 0; + if( event == DHT_EVENT_SEARCH_DONE ) { + tr_torinf(tor, "DHT announce done"); + tor->dhtAnnounceInProgress = 0; + } else { + tr_torinf(tor, "IPv6 DHT announce done"); + tor->dhtAnnounce6InProgress = 0; + } } } } @@ -403,32 +468,52 @@ callback( void *ignore UNUSED, int event, int tr_dhtAnnounce(tr_torrent *tor, tr_bool announce) { - int rc, status, numnodes; + int rc, status, numnodes, ret = 0; if( !tr_torrentAllowsDHT( tor ) ) return -1; - status = tr_dhtStatus( tor->session, &numnodes ); - if(status < TR_DHT_POOR ) { + status = tr_dhtStatus( tor->session, AF_INET, &numnodes ); + if(status >= TR_DHT_POOR ) { + rc = dht_search( tor->info.hash, + announce ? tr_sessionGetPeerPort(session) : 0, + AF_INET, callback, NULL); + if( rc >= 1 ) { + tr_torinf(tor, "Starting DHT announce (%s, %d nodes)", + tr_dhtPrintableStatus(status), numnodes); + tor->dhtAnnounceInProgress = TRUE; + ret = 1; + } else { + tr_torerr(tor, "DHT announce failed, errno = %d (%s, %d nodes)", + errno, tr_dhtPrintableStatus(status), numnodes); + } + } else { tr_tordbg(tor, "DHT not ready (%s, %d nodes)", tr_dhtPrintableStatus(status), numnodes); - return 0; } - rc = dht_search( dht_socket, tor->info.hash, - announce ? tr_sessionGetPeerPort(session) : 0, - callback, NULL); - - if( rc >= 1 ) { - tr_torinf(tor, "Starting DHT announce (%s, %d nodes)", - tr_dhtPrintableStatus(status), numnodes); - tor->dhtAnnounceInProgress = TRUE; - } else { - tr_torerr(tor, "DHT announce failed, errno = %d (%s, %d nodes)", - errno, tr_dhtPrintableStatus(status), numnodes); + if( tr_globalIPv6() ) { + status = tr_dhtStatus( tor->session, AF_INET6, &numnodes ); + if(status >= TR_DHT_POOR ) { + rc = dht_search( tor->info.hash, + announce ? tr_sessionGetPeerPort(session) : 0, + AF_INET6, callback, NULL); + + if( rc >= 1 ) { + tr_torinf(tor, "Starting IPv6 DHT announce (%s, %d nodes)", + tr_dhtPrintableStatus(status), numnodes); + tor->dhtAnnounce6InProgress = TRUE; + } else { + tr_torerr(tor, "IPv6 DHT announce failed, errno = %d (%s, %d nodes)", + errno, tr_dhtPrintableStatus(status), numnodes); + } + } else { + tr_tordbg(tor, "IPv6 DHT not ready (%s, %d nodes)", + tr_dhtPrintableStatus(status), numnodes); + } } - return 1; + return ret; } static void @@ -436,7 +521,7 @@ event_callback(int s, short type, void *ignore UNUSED ) { time_t tosleep; - if( dht_periodic(s, type == EV_READ, &tosleep, callback, NULL) < 0 ) { + if( dht_periodic( type == EV_READ, &tosleep, callback, NULL) < 0 ) { if(errno == EINTR) { tosleep = 0; } else { diff --git a/libtransmission/tr-dht.h b/libtransmission/tr-dht.h index 51c6ca9..83f2369 100644 --- a/libtransmission/tr-dht.h +++ b/libtransmission/tr-dht.h @@ -30,7 +30,7 @@ int tr_dhtInit( tr_session *, tr_address * ); void tr_dhtUninit( tr_session * ); tr_bool tr_dhtEnabled( const tr_session * ); tr_port tr_dhtPort ( const tr_session * ); -int tr_dhtStatus( tr_session *, int * setme_nodeCount ); +int tr_dhtStatus( tr_session *, int af, int * setme_nodeCount ); const char *tr_dhtPrintableStatus(int status); int tr_dhtAddNode( tr_session *, const tr_address *, tr_port, tr_bool bootstrap ); int tr_dhtAnnounce( tr_torrent *, tr_bool announce ); -- 1.6.5.2