mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-02-26 23:23:00 +00:00
4138 lines
118 KiB
C
4138 lines
118 KiB
C
|
/*
|
||
|
Copyright (c) 2009-2011 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. */
|
||
|
|
||
|
/* Sorry dude, we hacked up this code pretty good but its not C++ and the license is pure BSD.
|
||
|
Like Meatloaf says, 2 out of 3 ain't bad! But we needed a good base for some additions we needed */
|
||
|
|
||
|
|
||
|
#include "ks.h"
|
||
|
#include "sodium.h"
|
||
|
|
||
|
#ifndef MSG_CONFIRM
|
||
|
#define MSG_CONFIRM 0
|
||
|
#endif
|
||
|
|
||
|
KS_DECLARE(int) dht_blacklisted(const ks_sockaddr_t *sa)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(void) dht_hash(void *hash_return, int hash_size, const void *v1, int len1, const void *v2, int len2, const void *v3, int len3)
|
||
|
{
|
||
|
crypto_generichash_state state;
|
||
|
|
||
|
crypto_generichash_init(&state, NULL, 0, hash_size);
|
||
|
crypto_generichash_update(&state, v1, len1);
|
||
|
crypto_generichash_update(&state, v2, len2);
|
||
|
crypto_generichash_update(&state, v3, len3);
|
||
|
crypto_generichash_final(&state, (unsigned char *)hash_return, hash_size);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
KS_DECLARE(int) dht_random_bytes(void *buf, size_t size)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
|
||
|
#undef EAFNOSUPPORT
|
||
|
#define EAFNOSUPPORT WSAEAFNOSUPPORT
|
||
|
|
||
|
static int random(void)
|
||
|
{
|
||
|
return rand();
|
||
|
}
|
||
|
|
||
|
/* Windows Vista and later already provide the implementation. */
|
||
|
#if _WIN32_WINNT < 0x0600
|
||
|
extern const char *inet_ntop(int, const void *, char *, socklen_t);
|
||
|
#endif
|
||
|
|
||
|
#else
|
||
|
#endif
|
||
|
|
||
|
/* We set sin_family to 0 to mark unused slots. */
|
||
|
#if AF_INET == 0 || AF_INET6 == 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];
|
||
|
ks_sockaddr_t ss;
|
||
|
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 {
|
||
|
int af;
|
||
|
unsigned char first[20];
|
||
|
int count; /* number of nodes */
|
||
|
time_t time; /* time of last reply in this bucket */
|
||
|
struct node *nodes;
|
||
|
ks_sockaddr_t cached; /* the address of a likely candidate */
|
||
|
struct bucket *next;
|
||
|
};
|
||
|
|
||
|
struct search_node {
|
||
|
unsigned char id[20];
|
||
|
ks_sockaddr_t ss;
|
||
|
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;
|
||
|
int af;
|
||
|
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;
|
||
|
ks_sockaddr_t addr;
|
||
|
};
|
||
|
|
||
|
/* 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 hashes we're willing to track. */
|
||
|
#ifndef DHT_MAX_HASHES
|
||
|
#define DHT_MAX_HASHES 16384
|
||
|
#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, maxpeers;
|
||
|
struct peer *peers;
|
||
|
struct storage *next;
|
||
|
};
|
||
|
|
||
|
static struct storage * find_storage(dht_handle_t *h, const unsigned char *id);
|
||
|
static void flush_search_node(struct search_node *n, struct search *sr);
|
||
|
|
||
|
typedef enum {
|
||
|
DHT_MSG_INVALID = 0,
|
||
|
DHT_MSG_ERROR = 1,
|
||
|
DHT_MSG_REPLY = 2,
|
||
|
DHT_MSG_PING = 3,
|
||
|
DHT_MSG_FIND_NODE = 4,
|
||
|
DHT_MSG_GET_PEERS = 5,
|
||
|
DHT_MSG_ANNOUNCE_PEER = 6,
|
||
|
DHT_MSG_STORE_PUT = 7
|
||
|
} dht_msg_type_t;
|
||
|
|
||
|
#define WANT4 1
|
||
|
#define WANT6 2
|
||
|
|
||
|
static dht_msg_type_t parse_message(struct bencode *bencode_p,
|
||
|
unsigned char *tid_return, int *tid_len,
|
||
|
unsigned char *id_return);
|
||
|
|
||
|
static unsigned char *debug_printable(const unsigned char *buf, unsigned char *out, int buflen);
|
||
|
static void print_hex(FILE *f, const unsigned char *buf, int buflen);
|
||
|
static int is_martian(const ks_sockaddr_t *sa);
|
||
|
static int id_cmp(const unsigned char *restrict id1, const unsigned char *restrict id2);
|
||
|
static int lowbit(const unsigned char *id);
|
||
|
static int common_bits(const unsigned char *id1, const unsigned char *id2);
|
||
|
static int xorcmp(const unsigned char *id1, const unsigned char *id2, const unsigned char *ref);
|
||
|
static int in_bucket(const unsigned char *id, struct bucket *b);
|
||
|
static struct bucket *find_bucket(dht_handle_t *h, unsigned const char *id, int af);
|
||
|
static struct bucket *previous_bucket(dht_handle_t *h, struct bucket *b);
|
||
|
static struct node *find_node(dht_handle_t *h, const unsigned char *id, int af);
|
||
|
static struct node *random_node(struct bucket *b);
|
||
|
static int bucket_middle(struct bucket *b, unsigned char *id_return);
|
||
|
static int bucket_random(struct bucket *b, unsigned char *id_return);
|
||
|
static struct node *insert_node(dht_handle_t *h, struct node *node);
|
||
|
static int node_good(dht_handle_t *h, struct node *node);
|
||
|
static void make_tid(unsigned char *tid_return, const char *prefix, unsigned short seqno);
|
||
|
static int tid_match(const unsigned char *tid, const char *prefix, unsigned short *seqno_return);
|
||
|
static int send_cached_ping(dht_handle_t *h, struct bucket *b);
|
||
|
static void pinged(dht_handle_t *h, struct node *n, struct bucket *b);
|
||
|
static void blacklist_node(dht_handle_t *h, const unsigned char *id, const ks_sockaddr_t *sa);
|
||
|
static int node_blacklisted(dht_handle_t *h, const ks_sockaddr_t *sa);
|
||
|
static struct bucket *split_bucket(dht_handle_t *h, struct bucket *b);
|
||
|
static struct node *new_node(dht_handle_t *h, const unsigned char *id, const ks_sockaddr_t *sa, int confirm);
|
||
|
static int expire_buckets(dht_handle_t *h, struct bucket *b);
|
||
|
static struct search *find_search(dht_handle_t *h, unsigned short tid, int af);
|
||
|
static int insert_search_node(dht_handle_t *h, unsigned char *id,
|
||
|
const ks_sockaddr_t *sa,
|
||
|
struct search *sr, int replied,
|
||
|
unsigned char *token, int token_len);
|
||
|
static void flush_search_node(struct search_node *n, struct search *sr);
|
||
|
static void expire_searches(dht_handle_t *h);
|
||
|
static int search_send_get_peers(dht_handle_t *h, struct search *sr, struct search_node *n);
|
||
|
static void search_step(dht_handle_t *h, struct search *sr);
|
||
|
static struct search *new_search(dht_handle_t *h);
|
||
|
static void insert_search_bucket(dht_handle_t *h, struct bucket *b, struct search *sr);
|
||
|
static struct storage *find_storage(dht_handle_t *h, const unsigned char *id);
|
||
|
static int storage_store(dht_handle_t *h, const unsigned char *id, const ks_sockaddr_t *sa, unsigned short port);
|
||
|
static int expire_storage(dht_handle_t *h);
|
||
|
static int rotate_secrets(dht_handle_t *h);
|
||
|
static void make_token(dht_handle_t *h, const ks_sockaddr_t *sa, int old, unsigned char *token_return);
|
||
|
static int token_match(dht_handle_t *h, const unsigned char *token, int token_len, const ks_sockaddr_t *sa);
|
||
|
static void dump_bucket(dht_handle_t *h, FILE *f, struct bucket *b);
|
||
|
|
||
|
static void reset_poll(dht_handle_t *h);
|
||
|
static void clear_all_ip(dht_handle_t *h);
|
||
|
static int token_bucket(dht_handle_t *h);
|
||
|
static int neighbourhood_maintenance(dht_handle_t *h, int af);
|
||
|
static int bucket_maintenance(dht_handle_t *h, int af);
|
||
|
static int dht_send(dht_handle_t *h, const void *buf, size_t len, int flags, const ks_sockaddr_t *sa);
|
||
|
|
||
|
static int send_ping(dht_handle_t *h, const ks_sockaddr_t *sa, const unsigned char *tid, int tid_len);
|
||
|
static int send_pong(dht_handle_t *h, const ks_sockaddr_t *sa, const unsigned char *tid, int tid_len);
|
||
|
static int send_find_node(dht_handle_t *h, const ks_sockaddr_t *sa, const unsigned char *tid, int tid_len, const unsigned char *target, int target_len,
|
||
|
int want, int confirm);
|
||
|
static int send_nodes_peers(dht_handle_t *h, const ks_sockaddr_t *sa, const unsigned char *tid, int tid_len, const unsigned char *nodes, int nodes_len,
|
||
|
const unsigned char *nodes6, int nodes6_len, int af, struct storage *st, const unsigned char *token, int token_len);
|
||
|
static int insert_closest_node(unsigned char *nodes, int numnodes, const unsigned char *id, struct node *n);
|
||
|
static int buffer_closest_nodes(dht_handle_t *h, unsigned char *nodes, int numnodes, const unsigned char *id, struct bucket *b);
|
||
|
static int send_closest_nodes(dht_handle_t *h, const ks_sockaddr_t *sa, const unsigned char *tid, int tid_len, const unsigned char *id, int want,
|
||
|
int af, struct storage *st, const unsigned char *token, int token_len);
|
||
|
static int send_get_peers(dht_handle_t *h, const ks_sockaddr_t *sa, unsigned char *tid, int tid_len, unsigned char *infohash, int want, int confirm);
|
||
|
static int send_announce_peer(dht_handle_t *h, const ks_sockaddr_t *sa, unsigned char *tid, int tid_len, unsigned char *infohas, unsigned short port,
|
||
|
unsigned char *token, int token_len, int confirm);
|
||
|
static int send_peer_announced(dht_handle_t *h, const ks_sockaddr_t *sa, unsigned char *tid, int tid_len);
|
||
|
static int send_error(dht_handle_t *h, const ks_sockaddr_t *sa, unsigned char *tid, int tid_len, int code, const char *message);
|
||
|
|
||
|
static dht_msg_type_t parse_message(struct bencode *bencode_p, unsigned char *tid_return, int *tid_len, unsigned char *id_return);
|
||
|
static int b64encode(unsigned char *in, ks_size_t ilen, unsigned char *out, ks_size_t olen);
|
||
|
|
||
|
|
||
|
static const unsigned char zeroes[20] = {0};
|
||
|
//static const unsigned char v4prefix[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 };
|
||
|
|
||
|
#define MAX_TOKEN_BUCKET_TOKENS 400
|
||
|
|
||
|
/* 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
|
||
|
|
||
|
struct ks_dht_store_entry_s {
|
||
|
const char *key;
|
||
|
|
||
|
ks_time_t received; /* recieved timestamp */
|
||
|
ks_time_t last_announce;
|
||
|
ks_time_t expiration; /* When should 'my' message be automatically expired. If not set will be expired after 10 minutes */
|
||
|
|
||
|
/* Top level struct pointers. Will need to be freed */
|
||
|
struct bencode *bencode_message_raw;
|
||
|
struct bencode *payload_bencode;
|
||
|
cJSON *body;
|
||
|
|
||
|
/* Short cut accessor pointers. Do not free these. */
|
||
|
const char *content_type;
|
||
|
const char *payload_raw;
|
||
|
|
||
|
unsigned int serial;
|
||
|
ks_bool_t mine;
|
||
|
ks_pool_t *pool;
|
||
|
};
|
||
|
|
||
|
struct ks_dht_store_s {
|
||
|
ks_time_t next_expiring;
|
||
|
ks_hash_t *hash;
|
||
|
ks_pool_t *pool;
|
||
|
};
|
||
|
|
||
|
typedef struct {
|
||
|
char ip[80];
|
||
|
ks_sockaddr_t addr;
|
||
|
ks_socket_t sock;
|
||
|
} ks_ip_t;
|
||
|
|
||
|
struct dht_handle_s {
|
||
|
ks_pool_t *pool;
|
||
|
|
||
|
struct pollfd *pollsocks;
|
||
|
ks_ip_t **iptsocks;
|
||
|
ks_sockaddr_t **addrsocks;
|
||
|
uint32_t num_pollsocks;
|
||
|
|
||
|
//int dht_socket;
|
||
|
//int dht_socket6;
|
||
|
|
||
|
time_t search_time;
|
||
|
time_t confirm_nodes_time;
|
||
|
time_t rotate_secrets_time;
|
||
|
|
||
|
unsigned char myid[20];
|
||
|
int have_v;
|
||
|
unsigned char my_v[9];
|
||
|
unsigned char secret[8];
|
||
|
unsigned char oldsecret[8];
|
||
|
unsigned int port;
|
||
|
|
||
|
struct bucket *buckets;
|
||
|
struct bucket *buckets6;
|
||
|
struct storage *storage;
|
||
|
int numstorage;
|
||
|
|
||
|
struct search *searches;
|
||
|
int numsearches;
|
||
|
unsigned short search_id;
|
||
|
|
||
|
ks_sockaddr_t blacklist[DHT_MAX_BLACKLISTED];
|
||
|
int next_blacklisted;
|
||
|
|
||
|
ks_time_t now;
|
||
|
time_t mybucket_grow_time, mybucket6_grow_time;
|
||
|
time_t expire_stuff_time;
|
||
|
|
||
|
time_t token_bucket_time;
|
||
|
int token_bucket_tokens;
|
||
|
|
||
|
ks_dht_store_entry_json_cb *store_json_cb;
|
||
|
void *store_json_cb_arg;
|
||
|
|
||
|
ks_hash_t *iphash;
|
||
|
|
||
|
struct ks_dht_store_s *store;
|
||
|
|
||
|
dht_callback_t callback;
|
||
|
void *closure;
|
||
|
|
||
|
uint32_t ip4s;
|
||
|
uint32_t ip6s;
|
||
|
|
||
|
int af_flags;
|
||
|
|
||
|
int tosleep;
|
||
|
|
||
|
int autoroute;
|
||
|
|
||
|
int started;
|
||
|
};
|
||
|
|
||
|
static ks_ip_t *add_ip(dht_handle_t *h, const char *ip, int port, int family);
|
||
|
|
||
|
static void ks_dht_store_entry_destroy(struct ks_dht_store_entry_s **old_entry);
|
||
|
static int ks_dht_store_entry_create(struct dht_handle_s *h, struct bencode *msg, struct ks_dht_store_entry_s **new_entry, ks_time_t life, ks_bool_t mine);
|
||
|
static struct ks_dht_store_entry_s *ks_dht_store_fetch(struct ks_dht_store_s *store, char *key);
|
||
|
static int ks_dht_store_insert(struct ks_dht_store_s *store, struct ks_dht_store_entry_s *entry, ks_time_t now);
|
||
|
static int ks_dht_store_replace(struct ks_dht_store_s *store, struct ks_dht_store_entry_s *entry);
|
||
|
static void ks_dht_store_prune(struct ks_dht_store_s *store, ks_time_t now);
|
||
|
static int ks_dht_store_create(ks_pool_t *pool, struct ks_dht_store_s **new_store);
|
||
|
static void ks_dht_store_destroy(struct ks_dht_store_s **old_store);
|
||
|
|
||
|
|
||
|
KS_DECLARE(void) ks_dht_store_entry_json_cb_set(struct dht_handle_s *h, ks_dht_store_entry_json_cb *store_json_cb, void *arg)
|
||
|
{
|
||
|
h->store_json_cb = store_json_cb;
|
||
|
h->store_json_cb_arg = arg;
|
||
|
}
|
||
|
|
||
|
static unsigned char *debug_printable(const unsigned char *buf, unsigned char *out, int buflen)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < buflen; i++) {
|
||
|
out[i] = (buf[i] >= 32 && buf[i] <= 126) ? buf[i] : '.';
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
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]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int is_martian(const ks_sockaddr_t *sa)
|
||
|
{
|
||
|
switch(sa->family) {
|
||
|
case AF_INET: {
|
||
|
return (sa->port == 0);
|
||
|
}
|
||
|
case AF_INET6: {
|
||
|
return (sa->port == 0);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* 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 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;
|
||
|
}
|
||
|
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(dht_handle_t *h, unsigned const char *id, int af)
|
||
|
{
|
||
|
struct bucket *b = af == AF_INET ? h->buckets : h->buckets6;
|
||
|
|
||
|
if (b == NULL) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
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(dht_handle_t *h, struct bucket *b)
|
||
|
{
|
||
|
struct bucket *p = b->af == AF_INET ? h->buckets : h->buckets6;
|
||
|
|
||
|
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(dht_handle_t *h, const unsigned char *id, int af)
|
||
|
{
|
||
|
struct bucket *b = find_bucket(h, id, af);
|
||
|
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(dht_handle_t *h, struct node *node)
|
||
|
{
|
||
|
struct bucket *b = find_bucket(h, node->id, node->ss.family);
|
||
|
|
||
|
if (b == NULL) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
node->next = b->nodes;
|
||
|
b->nodes = node;
|
||
|
b->count++;
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
/* This is our definition of a known-good node. */
|
||
|
static int node_good(dht_handle_t *h, struct node *node)
|
||
|
{
|
||
|
return node->pinged <= 2 && node->reply_time >= h->now - 7200 && node->time >= h->now - 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;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Every bucket caches the address of a likely node. Ping it. */
|
||
|
static int send_cached_ping(dht_handle_t *h, struct bucket *b)
|
||
|
{
|
||
|
unsigned char tid[4];
|
||
|
int rc;
|
||
|
/* We set family to 0 when there's no cached node. */
|
||
|
if (b->cached.family == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Sending ping to cached node.\n");
|
||
|
make_tid(tid, "pn", 0);
|
||
|
rc = send_ping(h, &b->cached, tid, 4);
|
||
|
b->cached.family = 0;
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
/* Called whenever we send a request to a node, increases the ping count
|
||
|
and, if that reaches 3, sends a ping to a new candidate. */
|
||
|
static void pinged(dht_handle_t *h, struct node *n, struct bucket *b)
|
||
|
{
|
||
|
n->pinged++;
|
||
|
n->pinged_time = h->now;
|
||
|
if (n->pinged >= 3) {
|
||
|
send_cached_ping(h, b ? b : find_bucket(h, n->id, n->ss.family));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The internal blacklist is an LRU cache of nodes that have sent
|
||
|
incorrect messages. */
|
||
|
static void blacklist_node(dht_handle_t *h, const unsigned char *id, const ks_sockaddr_t *sa)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Blacklisting broken node.\n");
|
||
|
|
||
|
if (id) {
|
||
|
struct node *n;
|
||
|
struct search *sr;
|
||
|
/* Make the node easy to discard. */
|
||
|
n = find_node(h, id, sa->family);
|
||
|
if (n) {
|
||
|
n->pinged = 3;
|
||
|
pinged(h, n, NULL);
|
||
|
}
|
||
|
/* Discard it from any searches in progress. */
|
||
|
sr = h->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. */
|
||
|
ks_addr_copy(&h->blacklist[h->next_blacklisted], sa);
|
||
|
h->next_blacklisted = (h->next_blacklisted + 1) % DHT_MAX_BLACKLISTED;
|
||
|
}
|
||
|
|
||
|
static int node_blacklisted(dht_handle_t *h, const ks_sockaddr_t *sa)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (dht_blacklisted(sa)) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
for(i = 0; i < DHT_MAX_BLACKLISTED; i++) {
|
||
|
if (ks_addr_cmp(&h->blacklist[i], sa)) {
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Split a bucket into two equal parts. */
|
||
|
static struct bucket *split_bucket(dht_handle_t *h, struct bucket *b)
|
||
|
{
|
||
|
struct bucket *new;
|
||
|
struct node *nodes;
|
||
|
int rc;
|
||
|
unsigned char new_id[20];
|
||
|
|
||
|
if ((rc = bucket_middle(b, new_id)) < 0) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
new = ks_pool_alloc(h->pool, sizeof(struct bucket));
|
||
|
|
||
|
new->af = b->af;
|
||
|
|
||
|
send_cached_ping(h, 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(h, n);
|
||
|
}
|
||
|
return b;
|
||
|
}
|
||
|
|
||
|
/* 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(dht_handle_t *h, const unsigned char *id, const ks_sockaddr_t *sa, int confirm)
|
||
|
{
|
||
|
struct bucket *b = find_bucket(h, id, sa->family);
|
||
|
struct node *n;
|
||
|
int mybucket, split;
|
||
|
|
||
|
if (b == NULL) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (id_cmp(id, h->myid) == 0) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (is_martian(sa) || node_blacklisted(h, sa)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
mybucket = in_bucket(h->myid, b);
|
||
|
|
||
|
if (confirm == 2) {
|
||
|
b->time = h->now;
|
||
|
}
|
||
|
|
||
|
n = b->nodes;
|
||
|
while (n) {
|
||
|
if (id_cmp(n->id, id) == 0) {
|
||
|
if (confirm || n->time < h->now - 15 * 60) {
|
||
|
/* Known node. Update stuff. */
|
||
|
ks_addr_copy(&n->ss, sa);
|
||
|
|
||
|
if (confirm) {
|
||
|
n->time = h->now;
|
||
|
}
|
||
|
if (confirm >= 2) {
|
||
|
n->reply_time = h->now;
|
||
|
n->pinged = 0;
|
||
|
n->pinged_time = 0;
|
||
|
}
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
|
||
|
/* New node. */
|
||
|
|
||
|
if (mybucket) {
|
||
|
if (sa->family == AF_INET) {
|
||
|
h->mybucket_grow_time = h->now;
|
||
|
} else {
|
||
|
h->mybucket6_grow_time = h->now;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* First, try to get rid of a known-bad node. */
|
||
|
n = b->nodes;
|
||
|
while (n) {
|
||
|
if (n->pinged >= 3 && n->pinged_time < h->now - 15) {
|
||
|
memcpy(n->id, id, 20);
|
||
|
ks_addr_copy(&n->ss, sa);
|
||
|
n->time = confirm ? h->now : 0;
|
||
|
n->reply_time = confirm >= 2 ? h->now : 0;
|
||
|
n->pinged_time = 0;
|
||
|
n->pinged = 0;
|
||
|
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(h, n)) {
|
||
|
dubious = 1;
|
||
|
if (n->pinged_time < h->now - 15) {
|
||
|
unsigned char tid[4];
|
||
|
ks_log(KS_LOG_DEBUG, "Sending ping to dubious node.\n");
|
||
|
make_tid(tid, "pn", 0);
|
||
|
send_ping(h, &n->ss, tid, 4);
|
||
|
n->pinged++;
|
||
|
n->pinged_time = h->now;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
|
||
|
split = 0;
|
||
|
if (mybucket) {
|
||
|
if (!dubious) {
|
||
|
split = 1;
|
||
|
}
|
||
|
/* If there's only one bucket, split eagerly. This is
|
||
|
incorrect unless there's more than 8 nodes in the DHT. */
|
||
|
else if (b->af == AF_INET && h->buckets->next == NULL) {
|
||
|
split = 1;
|
||
|
} else if (b->af == AF_INET6 && h->buckets6->next == NULL) {
|
||
|
split = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (split) {
|
||
|
ks_log(KS_LOG_DEBUG, "Splitting.\n");
|
||
|
b = split_bucket(h, b);
|
||
|
return new_node(h, id, sa, confirm);
|
||
|
}
|
||
|
|
||
|
/* No space for this node. Cache it away for later. */
|
||
|
if (confirm || b->cached.family == 0) {
|
||
|
ks_addr_copy(&b->cached, sa);
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Create a new node. */
|
||
|
n = ks_pool_alloc(h->pool, sizeof(struct node));
|
||
|
|
||
|
memcpy(n->id, id, 20);
|
||
|
ks_addr_copy(&n->ss, sa);
|
||
|
n->time = confirm ? h->now : 0;
|
||
|
n->reply_time = confirm >= 2 ? h->now : 0;
|
||
|
n->next = b->nodes;
|
||
|
b->nodes = n;
|
||
|
b->count++;
|
||
|
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(dht_handle_t *h, struct bucket *b)
|
||
|
{
|
||
|
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;
|
||
|
ks_pool_free(h->pool, n);
|
||
|
}
|
||
|
|
||
|
p = b->nodes;
|
||
|
while (p) {
|
||
|
while (p->next && p->next->pinged >= 4) {
|
||
|
n = p->next;
|
||
|
p->next = n->next;
|
||
|
b->count--;
|
||
|
changed = 1;
|
||
|
ks_pool_free(h->pool, n);
|
||
|
}
|
||
|
p = p->next;
|
||
|
}
|
||
|
|
||
|
if (changed) {
|
||
|
send_cached_ping(h, b);
|
||
|
}
|
||
|
|
||
|
b = b->next;
|
||
|
}
|
||
|
h->expire_stuff_time = h->now + 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(dht_handle_t *h, unsigned short tid, int af)
|
||
|
{
|
||
|
struct search *sr = h->searches;
|
||
|
while (sr) {
|
||
|
if (sr->tid == tid && sr->af == af) {
|
||
|
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(dht_handle_t *h, unsigned char *id,
|
||
|
const ks_sockaddr_t *sa,
|
||
|
struct search *sr, int replied,
|
||
|
unsigned char *token, int token_len)
|
||
|
{
|
||
|
struct search_node *n;
|
||
|
int i, j;
|
||
|
|
||
|
if (sa->family != sr->af) {
|
||
|
ks_log(KS_LOG_DEBUG, "Attempted to insert node in the wrong family.\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
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:
|
||
|
|
||
|
ks_addr_copy(&n->ss, sa);
|
||
|
|
||
|
if (replied) {
|
||
|
n->replied = 1;
|
||
|
n->reply_time = h->now;
|
||
|
n->request_time = 0;
|
||
|
n->pinged = 0;
|
||
|
}
|
||
|
if (token) {
|
||
|
if (token_len >= 40) {
|
||
|
ks_log(KS_LOG_DEBUG, "Eek! Overlong token.\n");
|
||
|
assert(0);
|
||
|
} 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(dht_handle_t *h)
|
||
|
{
|
||
|
struct search *sr = h->searches, *previous = NULL;
|
||
|
|
||
|
while (sr) {
|
||
|
struct search *next = sr->next;
|
||
|
if (sr->step_time < h->now - DHT_SEARCH_EXPIRE_TIME) {
|
||
|
if (previous) {
|
||
|
previous->next = next;
|
||
|
} else {
|
||
|
h->searches = next;
|
||
|
}
|
||
|
ks_pool_free(h->pool, sr);
|
||
|
h->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(dht_handle_t *h, 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 < h->now - 15) {
|
||
|
n = &sr->nodes[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!n || n->pinged >= 3 || n->replied || n->request_time >= h->now - 15) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Sending get_peers.\n");
|
||
|
make_tid(tid, "gp", sr->tid);
|
||
|
send_get_peers(h, &n->ss, tid, 4, sr->id, -1, n->reply_time >= h->now - 15);
|
||
|
n->pinged++;
|
||
|
n->request_time = h->now;
|
||
|
/* If the node happens to be in our main routing table, mark it as pinged. */
|
||
|
if ((node = find_node(h, n->id, n->ss.family))) {
|
||
|
pinged(h, node, NULL);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* When a search is in progress, we periodically call search_step to send
|
||
|
further requests. */
|
||
|
static void search_step(dht_handle_t *h, struct search *sr)
|
||
|
{
|
||
|
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) {
|
||
|
int all_acked = 1;
|
||
|
if (sr->port == 0) {
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
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. While
|
||
|
I don't think this makes a lot of sense -- just sending a positive reply is just as good --, let's deal with it. */
|
||
|
if (n->token_len == 0) {
|
||
|
n->acked = 1;
|
||
|
}
|
||
|
if (!n->acked) {
|
||
|
all_acked = 0;
|
||
|
ks_log(KS_LOG_DEBUG, "Sending announce_peer.\n");
|
||
|
make_tid(tid, "ap", sr->tid);
|
||
|
send_announce_peer(h, &n->ss,
|
||
|
tid, 4, sr->id, sr->port,
|
||
|
n->token, n->token_len,
|
||
|
n->reply_time < h->now - 15);
|
||
|
n->pinged++;
|
||
|
n->request_time = h->now;
|
||
|
node = find_node(h, n->id, n->ss.family);
|
||
|
if (node) pinged(h, node, NULL);
|
||
|
}
|
||
|
j++;
|
||
|
}
|
||
|
if (all_acked) {
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
sr->step_time = h->now;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (sr->step_time + 15 >= h->now) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
j = 0;
|
||
|
for (i = 0; i < sr->numnodes; i++) {
|
||
|
j += search_send_get_peers(h, sr, &sr->nodes[i]);
|
||
|
if (j >= 3) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
sr->step_time = h->now;
|
||
|
return;
|
||
|
|
||
|
done:
|
||
|
sr->done = 1;
|
||
|
if (h->callback) {
|
||
|
h->callback(h->closure, sr->af == AF_INET ? KS_DHT_EVENT_SEARCH_DONE : KS_DHT_EVENT_SEARCH_DONE6, sr->id, NULL, 0);
|
||
|
}
|
||
|
sr->step_time = h->now;
|
||
|
}
|
||
|
|
||
|
static struct search *new_search(dht_handle_t *h)
|
||
|
{
|
||
|
struct search *sr, *oldest = NULL;
|
||
|
|
||
|
/* Find the oldest done search */
|
||
|
sr = h->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 < h->now - DHT_SEARCH_EXPIRE_TIME) {
|
||
|
return oldest;
|
||
|
}
|
||
|
|
||
|
/* Allocate a new slot. */
|
||
|
if (h->numsearches < DHT_MAX_SEARCHES) {
|
||
|
sr = ks_pool_alloc(h->pool, sizeof(struct search));
|
||
|
sr->next = h->searches;
|
||
|
h->searches = sr;
|
||
|
h->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(dht_handle_t *h, struct bucket *b, struct search *sr)
|
||
|
{
|
||
|
struct node *n;
|
||
|
n = b->nodes;
|
||
|
while (n) {
|
||
|
insert_search_node(h, n->id, &n->ss, sr, 0, NULL, 0);
|
||
|
n = n->next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Start a search. If port is non-zero, perform an announce when the
|
||
|
search is complete. */
|
||
|
KS_DECLARE(int) dht_search(dht_handle_t *h, const unsigned char *id, int port, int af, dht_callback_t callback, void *closure)
|
||
|
{
|
||
|
struct search *sr;
|
||
|
struct storage *st;
|
||
|
struct bucket *b = find_bucket(h, id, af);
|
||
|
|
||
|
if (b == NULL) {
|
||
|
errno = EAFNOSUPPORT;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (!callback) callback = h->callback;
|
||
|
if (!closure) closure = h->closure;
|
||
|
|
||
|
/* Try to answer this search locally. In a fully grown DHT this
|
||
|
is very unlikely, but people are running modified versions of
|
||
|
this code in private DHTs with very few nodes. What's wrong
|
||
|
with flooding? */
|
||
|
if (callback) {
|
||
|
st = find_storage(h, id);
|
||
|
if (st) {
|
||
|
int i;
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Found local data (%d peers).\n", st->numpeers);
|
||
|
|
||
|
for (i = 0; i < st->numpeers; i++) {
|
||
|
(*callback)(closure, st->peers[i].addr.family == AF_INET ? KS_DHT_EVENT_VALUES : KS_DHT_EVENT_VALUES6, id, (void *)&st->peers[i].addr, sizeof(st->peers[i].addr));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sr = h->searches;
|
||
|
while (sr) {
|
||
|
if (sr->af == af && 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 < h->now - 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(h);
|
||
|
if (sr == NULL) {
|
||
|
errno = ENOSPC;
|
||
|
return -1;
|
||
|
}
|
||
|
sr->af = af;
|
||
|
sr->tid = h->search_id++;
|
||
|
sr->step_time = 0;
|
||
|
memcpy(sr->id, id, 20);
|
||
|
sr->done = 0;
|
||
|
sr->numnodes = 0;
|
||
|
}
|
||
|
|
||
|
sr->port = port;
|
||
|
|
||
|
insert_search_bucket(h, b, sr);
|
||
|
|
||
|
if (sr->numnodes < SEARCH_NODES) {
|
||
|
struct bucket *p = previous_bucket(h, b);
|
||
|
if (b->next) {
|
||
|
insert_search_bucket(h, b->next, sr);
|
||
|
}
|
||
|
if (p) {
|
||
|
insert_search_bucket(h, p, sr);
|
||
|
}
|
||
|
}
|
||
|
if (sr->numnodes < SEARCH_NODES) {
|
||
|
insert_search_bucket(h, find_bucket(h, h->myid, af), sr);
|
||
|
}
|
||
|
|
||
|
search_step(h, sr);
|
||
|
h->search_time = h->now;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* A struct storage stores all the stored peer addresses for a given info hash. */
|
||
|
static struct storage *find_storage(dht_handle_t *h, const unsigned char *id)
|
||
|
{
|
||
|
struct storage *st = h->storage;
|
||
|
|
||
|
while(st) {
|
||
|
if (id_cmp(id, st->id) == 0) {
|
||
|
break;
|
||
|
}
|
||
|
st = st->next;
|
||
|
}
|
||
|
return st;
|
||
|
}
|
||
|
|
||
|
static int storage_store(dht_handle_t *h, const unsigned char *id, const ks_sockaddr_t *sa, unsigned short port)
|
||
|
{
|
||
|
int i;
|
||
|
struct storage *st;
|
||
|
|
||
|
st = find_storage(h, id);
|
||
|
|
||
|
if (st == NULL) {
|
||
|
if (h->numstorage >= DHT_MAX_HASHES) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
st = ks_pool_alloc(h->pool, sizeof(struct storage));
|
||
|
memcpy(st->id, id, 20);
|
||
|
st->next = h->storage;
|
||
|
h->storage = st;
|
||
|
h->numstorage++;
|
||
|
}
|
||
|
|
||
|
for(i = 0; i < st->numpeers; i++) {
|
||
|
if (ks_addr_cmp(&st->peers[i].addr, sa)) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i < st->numpeers) {
|
||
|
/* Already there, only need to refresh */
|
||
|
st->peers[i].time = h->now;
|
||
|
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) {
|
||
|
return 0;
|
||
|
}
|
||
|
n = st->maxpeers == 0 ? 2 : 2 * st->maxpeers;
|
||
|
n = MIN(n, DHT_MAX_PEERS);
|
||
|
|
||
|
if (!(new_peers = realloc(st->peers, n * sizeof(struct peer)))) {
|
||
|
return -1;
|
||
|
}
|
||
|
st->peers = new_peers;
|
||
|
st->maxpeers = n;
|
||
|
}
|
||
|
p = &st->peers[st->numpeers++];
|
||
|
p->time = h->now;
|
||
|
ks_addr_copy(&p->addr, sa);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int expire_storage(dht_handle_t *h)
|
||
|
{
|
||
|
struct storage *st = h->storage, *previous = NULL;
|
||
|
|
||
|
while (st) {
|
||
|
int i = 0;
|
||
|
while (i < st->numpeers) {
|
||
|
if (st->peers[i].time < h->now - 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;
|
||
|
ks_pool_free(h->pool, st);
|
||
|
st = previous->next;
|
||
|
} else {
|
||
|
h->storage = st->next;
|
||
|
ks_pool_free(h->pool, st);
|
||
|
st = h->storage;
|
||
|
}
|
||
|
|
||
|
h->numstorage--;
|
||
|
if (h->numstorage < 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Eek... numstorage became negative.\n");
|
||
|
h->numstorage = 0;
|
||
|
}
|
||
|
} else {
|
||
|
previous = st;
|
||
|
st = st->next;
|
||
|
}
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int rotate_secrets(dht_handle_t *h)
|
||
|
{
|
||
|
h->rotate_secrets_time = h->now + 900 + random() % 1800;
|
||
|
|
||
|
memcpy(h->oldsecret, h->secret, sizeof(h->secret));
|
||
|
randombytes_buf(h->secret, sizeof(h->secret));
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#ifndef TOKEN_SIZE
|
||
|
#define TOKEN_SIZE 8
|
||
|
#endif
|
||
|
|
||
|
static void make_token(dht_handle_t *h, const ks_sockaddr_t *sa, int old, unsigned char *token_return)
|
||
|
{
|
||
|
void *ip;
|
||
|
ks_size_t iplen;
|
||
|
unsigned short port;
|
||
|
|
||
|
ks_addr_raw_data(sa, &ip, &iplen);
|
||
|
port = htons(sa->port);
|
||
|
|
||
|
dht_hash(token_return, TOKEN_SIZE, old ? h->oldsecret : h->secret, sizeof(h->secret), ip, iplen, (unsigned char*)&port, 2);
|
||
|
}
|
||
|
|
||
|
static int token_match(dht_handle_t *h, const unsigned char *token, int token_len, const ks_sockaddr_t *sa)
|
||
|
{
|
||
|
unsigned char t[TOKEN_SIZE];
|
||
|
|
||
|
if (token_len != TOKEN_SIZE) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
make_token(h, sa, 0, t);
|
||
|
if (memcmp(t, token, TOKEN_SIZE) == 0) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
make_token(h, sa, 1, t);
|
||
|
if (memcmp(t, token, TOKEN_SIZE) == 0) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) dht_nodes(dht_handle_t *h, 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 = af == AF_INET ? h->buckets : h->buckets6;
|
||
|
|
||
|
while (b) {
|
||
|
struct node *n = b->nodes;
|
||
|
while (n) {
|
||
|
if (node_good(h, n)) {
|
||
|
good++;
|
||
|
if (n->time > n->reply_time) {
|
||
|
incoming++;
|
||
|
}
|
||
|
} else {
|
||
|
dubious++;
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
if (b->cached.family > 0) {
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
static void dump_bucket(dht_handle_t *h, FILE *f, struct bucket *b)
|
||
|
{
|
||
|
struct node *n = b->nodes;
|
||
|
int mine = in_bucket(h->myid, b);
|
||
|
int age = (int)(h->now - b->time);
|
||
|
int cached = b->cached.family;
|
||
|
fprintf(f, "Bucket ");
|
||
|
print_hex(f, b->first, 20);
|
||
|
fprintf(f, " count %d age %d%s%s:\n", b->count, age, mine ? " (mine)" : "", cached ? " (cached)" : "");
|
||
|
|
||
|
while (n) {
|
||
|
fprintf(f, " Node ");
|
||
|
print_hex(f, n->id, 20);
|
||
|
|
||
|
if (n->ss.family == AF_INET6) {
|
||
|
fprintf(f, " [%s]:%d ", n->ss.host, n->ss.port);
|
||
|
} else {
|
||
|
fprintf(f, " %s:%d ", n->ss.host, n->ss.port);
|
||
|
}
|
||
|
|
||
|
if (n->time != n->reply_time) {
|
||
|
fprintf(f, "age %ld, %ld", (long)(h->now - n->time), (long)(h->now - n->reply_time));
|
||
|
} else {
|
||
|
fprintf(f, "age %ld", (long)(h->now - n->time));
|
||
|
}
|
||
|
|
||
|
if (n->pinged) {
|
||
|
fprintf(f, " (%d)", n->pinged);
|
||
|
}
|
||
|
|
||
|
if (node_good(h, n)) {
|
||
|
fprintf(f, " (good)");
|
||
|
}
|
||
|
fprintf(f, "\n");
|
||
|
n = n->next;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(void) dht_dump_tables(dht_handle_t *h, FILE *f)
|
||
|
{
|
||
|
int i;
|
||
|
struct bucket *b;
|
||
|
struct storage *st = h->storage;
|
||
|
struct search *sr = h->searches;
|
||
|
|
||
|
fprintf(f, "My id ");
|
||
|
print_hex(f, h->myid, 20);
|
||
|
fprintf(f, "\n");
|
||
|
|
||
|
b = h->buckets;
|
||
|
while (b) {
|
||
|
dump_bucket(h, f, b);
|
||
|
b = b->next;
|
||
|
}
|
||
|
|
||
|
fprintf(f, "\n");
|
||
|
|
||
|
b = h->buckets6;
|
||
|
while (b) {
|
||
|
dump_bucket(h, f, b);
|
||
|
b = b->next;
|
||
|
}
|
||
|
|
||
|
while (sr) {
|
||
|
fprintf(f, "\nSearch%s id ", sr->af == AF_INET6 ? " (IPv6)" : "");
|
||
|
print_hex(f, sr->id, 20);
|
||
|
fprintf(f, " age %d%s\n", (int)(h->now - 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)(h->now - n->request_time));
|
||
|
}
|
||
|
fprintf(f, "%d", (int)(h->now - n->reply_time));
|
||
|
if (n->pinged) {
|
||
|
fprintf(f, " (%d)", n->pinged);
|
||
|
}
|
||
|
fprintf(f, "%s%s.\n", find_node(h, n->id, AF_INET) ? " (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[100];
|
||
|
if (st->peers[i].addr.family == AF_INET) {
|
||
|
ks_snprintf(buf, sizeof(buf), "%s", st->peers[i].addr.host);
|
||
|
} else if (st->peers[i].addr.family == AF_INET6) {
|
||
|
ks_snprintf(buf, sizeof(buf), "[%s]", st->peers[i].addr.host);
|
||
|
} else {
|
||
|
strcpy(buf, "???");
|
||
|
}
|
||
|
fprintf(f, " %s:%u (%ld)", buf, st->peers[i].addr.port, (long)(h->now - st->peers[i].time));
|
||
|
}
|
||
|
st = st->next;
|
||
|
}
|
||
|
|
||
|
fprintf(f, "\n\n");
|
||
|
fflush(f);
|
||
|
}
|
||
|
|
||
|
static void ks_dht_store_entry_destroy(struct ks_dht_store_entry_s **old_entry)
|
||
|
{
|
||
|
struct ks_dht_store_entry_s *entry = *old_entry;
|
||
|
ks_pool_t *pool = entry->pool;
|
||
|
*old_entry = NULL;
|
||
|
|
||
|
/* While setting these members to NULL is not required, defaulting to including them for easier debugging */
|
||
|
entry->key = NULL;
|
||
|
entry->content_type = NULL;
|
||
|
entry->payload_raw = NULL;
|
||
|
entry->pool = NULL;
|
||
|
|
||
|
if ( entry->bencode_message_raw ) {
|
||
|
ben_free(entry->bencode_message_raw);
|
||
|
entry->bencode_message_raw = NULL;
|
||
|
}
|
||
|
|
||
|
if ( entry->payload_bencode ) {
|
||
|
ben_free(entry->payload_bencode);
|
||
|
entry->payload_bencode = NULL;
|
||
|
}
|
||
|
|
||
|
if ( entry->body ) {
|
||
|
cJSON_Delete(entry->body);
|
||
|
entry->body = NULL;
|
||
|
}
|
||
|
|
||
|
ks_pool_free(pool, entry);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Entries can be created by a remote system 'pushing' a message to us, or the local system creating and sending the message. */
|
||
|
|
||
|
static int ks_dht_store_entry_create(struct dht_handle_s *h, struct bencode *msg, struct ks_dht_store_entry_s **new_entry, ks_time_t life, ks_bool_t mine)
|
||
|
{
|
||
|
struct ks_dht_store_entry_s *entry = NULL;
|
||
|
ks_time_t now = ks_time_now_sec();
|
||
|
|
||
|
entry = ks_pool_alloc(h->pool, sizeof(struct ks_dht_store_entry_s));
|
||
|
entry->pool = h->pool;
|
||
|
entry->received = now;
|
||
|
entry->expiration = now + life;
|
||
|
entry->last_announce = 0; /* TODO: Instead we should announce this one, and set to now */
|
||
|
entry->serial = 1;
|
||
|
entry->mine = mine;
|
||
|
|
||
|
entry->bencode_message_raw = msg;
|
||
|
entry->payload_raw = NULL;
|
||
|
|
||
|
entry->content_type = NULL;
|
||
|
entry->payload_bencode = NULL;
|
||
|
entry->body = NULL;
|
||
|
|
||
|
if ( msg ) {
|
||
|
struct bencode *key_args = ben_dict_get_by_str(msg, "a");
|
||
|
struct bencode *key_token = NULL;
|
||
|
struct bencode *key_v = NULL;
|
||
|
struct bencode *key_ct = NULL;
|
||
|
struct bencode *tmp_v = NULL;
|
||
|
|
||
|
if ( !key_args ) {
|
||
|
ks_log(KS_LOG_ERROR, "dht_store_entry requires an 'a' key in the message\n");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
key_token = ben_dict_get_by_str(key_args, "token");
|
||
|
if ( !key_token ) {
|
||
|
ks_log(KS_LOG_ERROR, "dht_store_entry requires an 'token' key in the message\n");
|
||
|
goto err;
|
||
|
}
|
||
|
entry->key = ben_str_val(key_token);
|
||
|
ks_log(KS_LOG_INFO, "dht_store_entry now with new key[%s]\n", entry->key);
|
||
|
|
||
|
key_v = ben_dict_get_by_str(key_args, "v");
|
||
|
if ( !key_v ) {
|
||
|
ks_log(KS_LOG_ERROR, "dht_store_entry requires an 'v' key in the message\n");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
tmp_v = ben_decode(ben_str_val(key_v), ben_str_len(key_v));
|
||
|
|
||
|
entry->payload_raw = ben_str_val(tmp_v);
|
||
|
entry->payload_bencode = ben_decode(entry->payload_raw, ben_str_len(tmp_v));
|
||
|
|
||
|
if ( !entry->payload_bencode ) {
|
||
|
ks_log(KS_LOG_WARNING, "dht_store_entry payload failed to parse as bencode object\n");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Payload: %s\n", ben_print(entry->payload_bencode));
|
||
|
|
||
|
if ( ! ben_is_dict( entry->payload_bencode ) ) {
|
||
|
ks_log(KS_LOG_DEBUG, "dht_store_entry is not a bencode dict. Legal, just not likely one of ours.\n");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
This is a custom key that SWITCHBLADE is adding to give the protocol decoder a hint as to the payload type.
|
||
|
If this key is not set, then we need to assume that the payload is binary buffer of a known length, likely not from SWITCHBLADE.
|
||
|
*/
|
||
|
key_ct = ben_dict_get_by_str(entry->payload_bencode, "ct");
|
||
|
if ( !key_ct ) {
|
||
|
ks_log(KS_LOG_DEBUG, "dht_store_entry without a 'ct' key to hint at payload content type. Legal, just not likely one of ours.\n");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
entry->content_type = ben_str_val(key_ct);
|
||
|
|
||
|
if ( !ben_cmp_with_str(key_ct, "json") ) {
|
||
|
struct bencode *key_b = ben_dict_get_by_str(entry->payload_bencode, "b");
|
||
|
int buf_len = ben_str_len(key_b);
|
||
|
char *buf = NULL;
|
||
|
|
||
|
buf = calloc(1, buf_len);
|
||
|
memcpy(buf, ben_str_val(key_b), buf_len);
|
||
|
|
||
|
entry->body = cJSON_Parse(buf);
|
||
|
free(buf);
|
||
|
buf = NULL;
|
||
|
|
||
|
if ( !entry->body ) {
|
||
|
ks_log(KS_LOG_ERROR, "dht_store_entry with json payload failed to json parse. Someone sent and signed an invalid message.\n");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if ( h->store_json_cb ) {
|
||
|
h->store_json_cb(h, entry->body, h->store_json_cb_arg);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
done:
|
||
|
*new_entry = entry;
|
||
|
return 0;
|
||
|
err:
|
||
|
ks_dht_store_entry_destroy(&entry);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static struct ks_dht_store_entry_s *ks_dht_store_fetch(struct ks_dht_store_s *store, char *key)
|
||
|
{
|
||
|
assert(store != NULL);
|
||
|
|
||
|
return ks_hash_search(store->hash, (void *)key, 0);
|
||
|
}
|
||
|
|
||
|
static int ks_dht_store_insert(struct ks_dht_store_s *store, struct ks_dht_store_entry_s *entry, ks_time_t now)
|
||
|
{
|
||
|
return ks_hash_insert(store->hash, (void *)entry->key, entry);
|
||
|
}
|
||
|
|
||
|
static int ks_dht_store_replace(struct ks_dht_store_s *store, struct ks_dht_store_entry_s *entry)
|
||
|
{
|
||
|
struct ks_dht_store_entry_s *val = ks_hash_remove(store->hash, (void *) entry->key);
|
||
|
|
||
|
if ( val ) {
|
||
|
ks_dht_store_entry_destroy(&val);
|
||
|
}
|
||
|
|
||
|
return ks_hash_insert(store->hash, (void *) entry->key, entry);
|
||
|
}
|
||
|
|
||
|
static void ks_dht_store_prune(struct ks_dht_store_s *store, ks_time_t now)
|
||
|
{
|
||
|
(void) store;
|
||
|
(void) now;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* TODO: Look into using the ks_hash automatic destructor functionality. */
|
||
|
static int ks_dht_store_create(ks_pool_t *pool, struct ks_dht_store_s **new_store)
|
||
|
{
|
||
|
struct ks_dht_store_s *store = NULL;
|
||
|
|
||
|
store = ks_pool_alloc(pool, sizeof(struct ks_dht_store_s));
|
||
|
store->next_expiring = 0;
|
||
|
store->pool = pool;
|
||
|
|
||
|
ks_hash_create(&store->hash, KS_HASH_MODE_DEFAULT, KS_HASH_FLAG_RWLOCK, pool);
|
||
|
|
||
|
*new_store = store;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void ks_dht_store_destroy(struct ks_dht_store_s **old_store)
|
||
|
{
|
||
|
struct ks_dht_store_s *store = *old_store;
|
||
|
ks_hash_iterator_t *itt = NULL;
|
||
|
ks_pool_t *pool = store->pool;
|
||
|
*old_store = NULL;
|
||
|
|
||
|
ks_hash_write_lock(store->hash);
|
||
|
for (itt = ks_hash_first(store->hash, KS_UNLOCKED); itt; itt = ks_hash_next(&itt)) {
|
||
|
const void *key = NULL;
|
||
|
struct ks_dht_store_entry_s *val = NULL;
|
||
|
|
||
|
ks_hash_this(itt, &key, NULL, (void **) &val);
|
||
|
ks_hash_remove(store->hash, (char *)key);
|
||
|
|
||
|
ks_dht_store_entry_destroy(&val);
|
||
|
}
|
||
|
ks_hash_write_unlock(store->hash);
|
||
|
|
||
|
ks_hash_destroy(&store->hash);
|
||
|
|
||
|
ks_pool_free(pool, store);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void reset_poll(dht_handle_t *h)
|
||
|
{
|
||
|
int i = 0, socks = h->ip4s + h->ip6s;
|
||
|
ks_hash_iterator_t *itt;
|
||
|
|
||
|
if (!h->iphash) return;
|
||
|
|
||
|
if (h->num_pollsocks < socks) {
|
||
|
h->num_pollsocks = socks;
|
||
|
h->pollsocks = (struct pollfd *)ks_pool_resize(h->pool, (void *)h->pollsocks, sizeof(struct pollfd) * h->num_pollsocks);
|
||
|
h->iptsocks = (ks_ip_t **) ks_pool_resize(h->pool, (void *)h->iptsocks, sizeof(ks_ip_t *) * h->num_pollsocks);
|
||
|
h->addrsocks = (ks_sockaddr_t **) ks_pool_resize(h->pool, (void *)h->addrsocks, sizeof(ks_sockaddr_t *) * h->num_pollsocks);
|
||
|
ks_log(KS_LOG_DEBUG, "Resize poll array to %d\n", h->num_pollsocks);
|
||
|
}
|
||
|
|
||
|
for (itt = ks_hash_first(h->iphash, KS_UNLOCKED); itt; itt = ks_hash_next(&itt)) {
|
||
|
const void *key;
|
||
|
void *val;
|
||
|
ks_ip_t *ipt;
|
||
|
|
||
|
ks_hash_this(itt, &key, NULL, &val);
|
||
|
|
||
|
ipt = (ks_ip_t *) val;
|
||
|
|
||
|
h->pollsocks[i].fd = ipt->sock;
|
||
|
h->pollsocks[i].events = POLLIN | POLLERR;
|
||
|
h->iptsocks[i] = ipt;
|
||
|
h->addrsocks[i] = &ipt->addr;
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(ks_status_t) ks_dht_get_bind_addrs(dht_handle_t *h, const ks_sockaddr_t ***addrs, ks_size_t *addrlen)
|
||
|
{
|
||
|
*addrs = (const ks_sockaddr_t **) h->addrsocks;
|
||
|
*addrlen = h->num_pollsocks;
|
||
|
|
||
|
return KS_STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(ks_status_t) ks_dht_one_loop(dht_handle_t *h, int timeout)
|
||
|
{
|
||
|
ks_status_t status;
|
||
|
int s, i;
|
||
|
unsigned char buf[65536] = {0};
|
||
|
ks_size_t bytes = sizeof(buf);
|
||
|
|
||
|
if (!h->started) {
|
||
|
return KS_STATUS_FAIL;
|
||
|
}
|
||
|
|
||
|
|
||
|
reset_poll(h);
|
||
|
|
||
|
if (!timeout) timeout = h->tosleep * 1000;
|
||
|
|
||
|
|
||
|
s = ks_poll(h->pollsocks, h->num_pollsocks, timeout);
|
||
|
|
||
|
if (s < 0) {
|
||
|
return KS_STATUS_FAIL;
|
||
|
}
|
||
|
|
||
|
if (s == 0) {
|
||
|
dht_periodic(h, buf, 0, NULL);
|
||
|
return KS_STATUS_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < h->num_pollsocks; i++) {
|
||
|
if ((h->pollsocks[i].revents & POLLIN)) {
|
||
|
ks_sockaddr_t remote_addr = KS_SA_INIT;
|
||
|
|
||
|
remote_addr.family = h->iptsocks[i]->addr.family;
|
||
|
if ((status = ks_socket_recvfrom(h->pollsocks[i].fd, buf, &bytes, &remote_addr)) == KS_STATUS_SUCCESS) {
|
||
|
// git rid of tosleep and convert it to non-blocking counter so you can still call this in a loop and just return timeout till tosleep expired
|
||
|
// beginning of rabbit hole to change references to addrs to ks_addrs instead and stop passing sockaddr and len all over the place.
|
||
|
dht_periodic(h, buf, bytes, &remote_addr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return KS_STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static void clear_all_ip(dht_handle_t *h)
|
||
|
{
|
||
|
ks_hash_iterator_t *itt;
|
||
|
|
||
|
if (!h->iphash) return;
|
||
|
|
||
|
ks_hash_write_lock(h->iphash);
|
||
|
for (itt = ks_hash_first(h->iphash, KS_UNLOCKED); itt; itt = ks_hash_next(&itt)) {
|
||
|
const void *key;
|
||
|
void *val;
|
||
|
ks_ip_t *ipt;
|
||
|
|
||
|
ks_hash_this(itt, &key, NULL, &val);
|
||
|
|
||
|
ipt = (ks_ip_t *) val;
|
||
|
|
||
|
ks_socket_close(&ipt->sock);
|
||
|
ks_pool_free(h->pool, ipt);
|
||
|
|
||
|
if (ipt->addr.family == AF_INET) {
|
||
|
h->ip4s--;
|
||
|
} else {
|
||
|
h->ip6s--;
|
||
|
}
|
||
|
|
||
|
ks_hash_remove(h->iphash, (char *)key);
|
||
|
}
|
||
|
ks_hash_write_unlock(h->iphash);
|
||
|
|
||
|
ks_hash_destroy(&h->iphash);
|
||
|
|
||
|
}
|
||
|
|
||
|
static ks_ip_t *add_ip(dht_handle_t *h, const char *ip, int port, int family)
|
||
|
{
|
||
|
ks_ip_t *ipt;
|
||
|
|
||
|
ks_assert(h);
|
||
|
ks_assert(ip);
|
||
|
|
||
|
if (!port) port = h->port;
|
||
|
|
||
|
if (family == AF_INET) {
|
||
|
h->af_flags |= KS_DHT_AF_INET4;
|
||
|
|
||
|
if (!h->buckets) {
|
||
|
h->buckets = ks_pool_alloc(h->pool, sizeof(*h->buckets));
|
||
|
h->buckets->af = AF_INET;
|
||
|
}
|
||
|
|
||
|
} else if (family == AF_INET6) {
|
||
|
h->af_flags |= KS_DHT_AF_INET6;
|
||
|
|
||
|
if (!h->buckets6) {
|
||
|
h->buckets6 = ks_pool_alloc(h->pool, sizeof(*h->buckets6));
|
||
|
h->buckets6->af = AF_INET6;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Adding bind ip: %s port: %d family:%d\n", ip, port, family);
|
||
|
|
||
|
ipt = ks_pool_alloc(h->pool, sizeof(*ipt));
|
||
|
ipt->sock = KS_SOCK_INVALID;
|
||
|
|
||
|
ks_set_string(ipt->ip, ip);
|
||
|
|
||
|
ks_addr_set(&ipt->addr, ip, port, family);
|
||
|
|
||
|
if ((ipt->sock = socket(family, SOCK_DGRAM, IPPROTO_UDP)) == KS_SOCK_INVALID) {
|
||
|
ks_log(KS_LOG_ERROR, "Socket Error\n");
|
||
|
ks_pool_free(h->pool, ipt);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (ks_addr_bind(ipt->sock, &ipt->addr) != KS_STATUS_SUCCESS) {
|
||
|
ks_log(KS_LOG_ERROR, "Error Adding bind ip: %s port: %d sock: %d (%s)\n", ip, port, ipt->sock, strerror(errno));
|
||
|
ks_socket_close(&ipt->sock);
|
||
|
ks_pool_free(h->pool, ipt);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
ks_socket_option(ipt->sock, SO_REUSEADDR, KS_TRUE);
|
||
|
ks_socket_option(ipt->sock, KS_SO_NONBLOCK, KS_TRUE);
|
||
|
|
||
|
ks_hash_insert(h->iphash, (void *)ipt->ip, ipt);
|
||
|
|
||
|
if (family == AF_INET) {
|
||
|
h->ip4s++;
|
||
|
} else {
|
||
|
h->ip6s++;
|
||
|
}
|
||
|
|
||
|
reset_poll(h);
|
||
|
|
||
|
return ipt;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(void) ks_dht_set_port(dht_handle_t *h, unsigned int port)
|
||
|
{
|
||
|
h->port = port;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(void) ks_dht_set_v(dht_handle_t *h, const unsigned char *v)
|
||
|
{
|
||
|
if (v) {
|
||
|
memcpy(h->my_v, "1:v4:", 5);
|
||
|
memcpy(h->my_v + 5, v, 4);
|
||
|
h->have_v = 1;
|
||
|
} else {
|
||
|
h->have_v = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(ks_status_t) ks_dht_add_ip(dht_handle_t *h, char *ip, int port)
|
||
|
{
|
||
|
int family = AF_INET;
|
||
|
|
||
|
if (strchr(ip, ':')) {
|
||
|
family = AF_INET6;
|
||
|
}
|
||
|
|
||
|
return add_ip(h, ip, port, family) ? KS_STATUS_SUCCESS : KS_STATUS_FAIL;
|
||
|
}
|
||
|
|
||
|
|
||
|
KS_DECLARE(void) ks_dht_set_callback(dht_handle_t *h, dht_callback_t callback, void *closure)
|
||
|
{
|
||
|
h->callback = callback;
|
||
|
h->closure = closure;
|
||
|
}
|
||
|
|
||
|
|
||
|
KS_DECLARE(void) ks_dht_set_param(dht_handle_t *h, ks_dht_param_t param, ks_bool_t val)
|
||
|
{
|
||
|
switch(param) {
|
||
|
case DHT_PARAM_AUTOROUTE:
|
||
|
h->autoroute = val;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(void) ks_dht_start(dht_handle_t *h)
|
||
|
{
|
||
|
char ip[48] = "";
|
||
|
int mask = 0;
|
||
|
|
||
|
if (h->started) return;
|
||
|
|
||
|
if ((h->af_flags & KS_DHT_AF_INET4) && !h->ip4s) {
|
||
|
ks_find_local_ip(ip, sizeof(ip), &mask, AF_INET, NULL);
|
||
|
add_ip(h, ip, 0, AF_INET);
|
||
|
}
|
||
|
|
||
|
if ((h->af_flags & KS_DHT_AF_INET6) && !h->ip6s) {
|
||
|
ks_find_local_ip(ip, sizeof(ip), &mask, AF_INET6, NULL);
|
||
|
add_ip(h, ip, 0, AF_INET6);
|
||
|
}
|
||
|
|
||
|
h->started = 1;
|
||
|
|
||
|
}
|
||
|
|
||
|
//KS_DECLARE(int) dht_init(dht_handle_t **handle, int s, int s6, const unsigned char *id, const unsigned char *v, unsigned int port)
|
||
|
KS_DECLARE(ks_status_t) ks_dht_init(dht_handle_t **handle, ks_dht_af_flag_t af_flags, const unsigned char *id, unsigned int port)
|
||
|
{
|
||
|
int rc;
|
||
|
dht_handle_t *h;
|
||
|
ks_pool_t *pool;
|
||
|
|
||
|
ks_pool_open(&pool);
|
||
|
*handle = h = ks_pool_alloc(pool, sizeof(dht_handle_t));
|
||
|
|
||
|
h->pool = pool;
|
||
|
h->searches = NULL;
|
||
|
h->numsearches = 0;
|
||
|
|
||
|
if (port) {
|
||
|
h->port = port;
|
||
|
} else {
|
||
|
h->port = 5309;
|
||
|
}
|
||
|
|
||
|
h->af_flags = af_flags;
|
||
|
|
||
|
|
||
|
ks_hash_create(&h->iphash, KS_HASH_MODE_DEFAULT, KS_HASH_FLAG_RWLOCK, h->pool);
|
||
|
|
||
|
h->store_json_cb = NULL;
|
||
|
h->store_json_cb_arg = NULL;
|
||
|
|
||
|
h->storage = NULL;
|
||
|
h->numstorage = 0;
|
||
|
|
||
|
if (!id) {
|
||
|
//ks_random_string((char *)h->myid, 20, NULL);
|
||
|
randombytes_buf(h->myid, 20);
|
||
|
} else {
|
||
|
memcpy(h->myid, id, 20);
|
||
|
}
|
||
|
|
||
|
h->have_v = 0;
|
||
|
|
||
|
h->now = ks_time_now_sec();
|
||
|
|
||
|
h->mybucket_grow_time = h->now;
|
||
|
h->mybucket6_grow_time = h->now;
|
||
|
h->confirm_nodes_time = h->now + random() % 3;
|
||
|
|
||
|
h->search_id = random() & 0xFFFF;
|
||
|
h->search_time = 0;
|
||
|
|
||
|
h->next_blacklisted = 0;
|
||
|
|
||
|
h->token_bucket_time = h->now;
|
||
|
h->token_bucket_tokens = MAX_TOKEN_BUCKET_TOKENS;
|
||
|
|
||
|
memset(h->secret, 0, sizeof(h->secret));
|
||
|
rc = rotate_secrets(h);
|
||
|
if (rc < 0)
|
||
|
goto fail;
|
||
|
|
||
|
|
||
|
expire_buckets(h, h->buckets);
|
||
|
expire_buckets(h, h->buckets6);
|
||
|
|
||
|
ks_dht_store_create(h->pool, &h->store);
|
||
|
|
||
|
return KS_STATUS_SUCCESS;
|
||
|
|
||
|
fail:
|
||
|
ks_pool_free(h->pool, h->buckets);
|
||
|
h->buckets = NULL;
|
||
|
ks_pool_free(h->pool, h->buckets6);
|
||
|
h->buckets6 = NULL;
|
||
|
return KS_STATUS_FAIL;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) dht_uninit(dht_handle_t **handle)
|
||
|
{
|
||
|
dht_handle_t *h;
|
||
|
ks_pool_t *pool;
|
||
|
|
||
|
ks_assert(handle && *handle);
|
||
|
|
||
|
h = *handle;
|
||
|
*handle = NULL;
|
||
|
|
||
|
clear_all_ip(h);
|
||
|
|
||
|
while (h->buckets) {
|
||
|
struct bucket *b = h->buckets;
|
||
|
h->buckets = b->next;
|
||
|
while (b->nodes) {
|
||
|
struct node *n = b->nodes;
|
||
|
b->nodes = n->next;
|
||
|
ks_pool_free(h->pool, n);
|
||
|
}
|
||
|
ks_pool_free(h->pool, b);
|
||
|
}
|
||
|
|
||
|
while (h->buckets6) {
|
||
|
struct bucket *b = h->buckets6;
|
||
|
h->buckets6 = b->next;
|
||
|
while (b->nodes) {
|
||
|
struct node *n = b->nodes;
|
||
|
b->nodes = n->next;
|
||
|
ks_pool_free(h->pool, n);
|
||
|
}
|
||
|
ks_pool_free(h->pool, b);
|
||
|
}
|
||
|
|
||
|
while (h->storage) {
|
||
|
struct storage *st = h->storage;
|
||
|
h->storage = h->storage->next;
|
||
|
ks_pool_free(h->pool, st->peers);
|
||
|
ks_pool_free(h->pool, st);
|
||
|
}
|
||
|
|
||
|
while (h->searches) {
|
||
|
struct search *sr = h->searches;
|
||
|
h->searches = h->searches->next;
|
||
|
ks_pool_free(h->pool, sr);
|
||
|
}
|
||
|
|
||
|
ks_dht_store_destroy(&h->store);
|
||
|
pool = h->pool;
|
||
|
h->pool = NULL;
|
||
|
ks_pool_free(pool, h);
|
||
|
ks_pool_close(&pool);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* Rate control for requests we receive. */
|
||
|
|
||
|
static int token_bucket(dht_handle_t *h)
|
||
|
{
|
||
|
if (h->token_bucket_tokens == 0) {
|
||
|
h->token_bucket_tokens = MIN(MAX_TOKEN_BUCKET_TOKENS, 100 * (h->now - h->token_bucket_time));
|
||
|
h->token_bucket_time = h->now;
|
||
|
}
|
||
|
|
||
|
if (h->token_bucket_tokens == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
h->token_bucket_tokens--;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int neighbourhood_maintenance(dht_handle_t *h, int af)
|
||
|
{
|
||
|
unsigned char id[20];
|
||
|
struct bucket *b = find_bucket(h, h->myid, af);
|
||
|
struct bucket *q;
|
||
|
struct node *n;
|
||
|
|
||
|
if (b == NULL) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
memcpy(id, h->myid, 20);
|
||
|
id[19] = random() & 0xFF;
|
||
|
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(h, b);
|
||
|
if (r && r->count > 0) {
|
||
|
q = r;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (q) {
|
||
|
/* Since our node-id is the same in both DHTs, it's probably
|
||
|
profitable to query both families. */
|
||
|
|
||
|
n = random_node(q);
|
||
|
if (n) {
|
||
|
unsigned char tid[4];
|
||
|
|
||
|
const char *msg;
|
||
|
|
||
|
|
||
|
if ((h->af_flags & KS_DHT_AF_INET6) && (h->af_flags & KS_DHT_AF_INET4)) {
|
||
|
msg = "v4 and v6";
|
||
|
} else if (h->af_flags & KS_DHT_AF_INET6) {
|
||
|
msg = "v6";
|
||
|
} else {
|
||
|
msg = "v4";
|
||
|
}
|
||
|
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Sending find_node for %s on %s neighborhood maintenance.\n", msg, af == AF_INET6 ? "IPv6" : "IPv4");
|
||
|
make_tid(tid, "fn", 0);
|
||
|
send_find_node(h, &n->ss, tid, 4, id, sizeof(id), h->af_flags, n->reply_time >= h->now - 15);
|
||
|
pinged(h, n, q);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int bucket_maintenance(dht_handle_t *h, int af)
|
||
|
{
|
||
|
struct bucket *b;
|
||
|
|
||
|
b = af == AF_INET ? h->buckets : h->buckets6;
|
||
|
|
||
|
while (b) {
|
||
|
struct bucket *q;
|
||
|
if (b->time < h->now - 600) {
|
||
|
/* This bucket hasn't seen any positive confirmation 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(h, b);
|
||
|
if (r && r->count > 0) {
|
||
|
q = r;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (q) {
|
||
|
n = random_node(q);
|
||
|
if (n) {
|
||
|
unsigned char tid[4];
|
||
|
int want = -1;
|
||
|
|
||
|
if ((h->af_flags & KS_DHT_AF_INET4) && (h->af_flags & KS_DHT_AF_INET6)) {
|
||
|
struct bucket *otherbucket;
|
||
|
otherbucket = find_bucket(h, id, af == AF_INET ? AF_INET6 : AF_INET);
|
||
|
if (otherbucket && otherbucket->count < 8) {
|
||
|
/* The corresponding bucket in the other family is emptyish -- querying both is useful. */
|
||
|
want = WANT4 | WANT6;
|
||
|
} else if (random() % 37 == 0) {
|
||
|
/* Most of the time, this just adds overhead.
|
||
|
However, it might help stitch back one of
|
||
|
the DHTs after a network collapse, so query
|
||
|
both, but only very occasionally. */
|
||
|
want = WANT4 | WANT6;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Sending find_node for%s bucket maintenance.\n", af == AF_INET6 ? " IPv6" : "");
|
||
|
make_tid(tid, "fn", 0);
|
||
|
send_find_node(h, &n->ss, tid, 4, id, sizeof(id), want, n->reply_time >= h->now - 15);
|
||
|
pinged(h, 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;
|
||
|
}
|
||
|
|
||
|
|
||
|
KS_DECLARE(int) dht_periodic(dht_handle_t *h, const void *buf, size_t buflen, ks_sockaddr_t *from)
|
||
|
//KS_DECLARE(int) dht_periodic(dht_handle_t *h, const void *buf, size_t buflen, const struct sockaddr *from, int fromlen,
|
||
|
// time_t *tosleep, dht_callback *callback, void *closure)
|
||
|
{
|
||
|
unsigned char *logmsg = NULL;
|
||
|
h->now = ks_time_now_sec();
|
||
|
|
||
|
if (buflen > 0) {
|
||
|
dht_msg_type_t message;
|
||
|
unsigned char tid[16], id[20], info_hash[20], target[20];
|
||
|
unsigned char nodes[26*16], nodes6[38*16], token[128] = {0};
|
||
|
int tid_len = 16, token_len = 0;
|
||
|
int nodes_len = 26*16, nodes6_len = 38*16;
|
||
|
unsigned short port = 0;
|
||
|
unsigned char values[2048], values6[2048];
|
||
|
int values_len = 2048, values6_len = 2048;
|
||
|
int want = 0;
|
||
|
unsigned short ttid;
|
||
|
struct bencode *msg_ben = NULL;
|
||
|
struct bencode *key_args = NULL; /* Request args */
|
||
|
struct bencode *key_info_hash = NULL;
|
||
|
struct bencode *key_want = NULL;
|
||
|
struct bencode *key_token = NULL;
|
||
|
struct bencode *key_port = NULL;
|
||
|
struct bencode *key_target = NULL;
|
||
|
|
||
|
struct bencode *key_resp = NULL; /* Response values */
|
||
|
struct bencode *key_values = NULL;
|
||
|
struct bencode *key_values6 = NULL;
|
||
|
struct bencode *key_nodes = NULL;
|
||
|
struct bencode *key_nodes6 = NULL;
|
||
|
|
||
|
if (is_martian(from)) {
|
||
|
goto dontread;
|
||
|
}
|
||
|
|
||
|
if (node_blacklisted(h, from)) {
|
||
|
ks_log(KS_LOG_DEBUG, "Received packet from blacklisted node.\n");
|
||
|
goto dontread;
|
||
|
}
|
||
|
|
||
|
if (((char*)buf)[buflen] != '\0') {
|
||
|
ks_log(KS_LOG_DEBUG, "Unterminated message.\n");
|
||
|
errno = EINVAL;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
msg_ben = ben_decode((const void *) buf, buflen);
|
||
|
if ( !msg_ben ) {
|
||
|
ks_log(KS_LOG_DEBUG, "Received invalid message. Unable to ben_decode it.\n");
|
||
|
goto dontread;
|
||
|
}
|
||
|
|
||
|
message = parse_message(msg_ben, tid, &tid_len, id);
|
||
|
ks_log(KS_LOG_DEBUG, "Received bencode message[%d] from [%s] port (%d): \n\n%s\n", message, from->host, from->port, ben_print(msg_ben));
|
||
|
|
||
|
if (id_cmp(id, zeroes) == 0) {
|
||
|
message = DHT_MSG_INVALID;
|
||
|
} else if (id_cmp(id, h->myid) == 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Received message from self.\n");
|
||
|
goto dontread;
|
||
|
}
|
||
|
|
||
|
if (message > DHT_MSG_REPLY) {
|
||
|
/* Rate limit requests. */
|
||
|
if (!token_bucket(h)) {
|
||
|
ks_log(KS_LOG_DEBUG, "Dropping request due to rate limiting.\n");
|
||
|
goto dontread;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
key_args = ben_dict_get_by_str(msg_ben, "a");
|
||
|
if ( key_args ) {
|
||
|
key_info_hash = ben_dict_get_by_str(key_args, "info_hash");
|
||
|
|
||
|
if ( key_info_hash ) {
|
||
|
memcpy(info_hash, ben_str_val(key_info_hash), ben_str_len(key_info_hash));
|
||
|
}
|
||
|
|
||
|
key_want = ben_dict_get_by_str(key_args, "want");
|
||
|
|
||
|
if ( key_want && ben_is_list(key_want)) {
|
||
|
int x = 0;
|
||
|
for( x = 0; x < ben_list_len(key_want); x++ ) {
|
||
|
struct bencode *key_tmp = ben_list_get(key_want, x);
|
||
|
if ( !ben_cmp_with_str(key_tmp, "n4") ) {
|
||
|
want |= WANT4;
|
||
|
} else if ( !ben_cmp_with_str(key_tmp, "n6") ) {
|
||
|
want |= WANT6;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
want = WANT4;
|
||
|
}
|
||
|
|
||
|
key_target = ben_dict_get_by_str(key_args, "target");
|
||
|
|
||
|
if ( key_target ) {
|
||
|
memcpy(target, ben_str_val(key_target), ben_str_len(key_target));
|
||
|
}
|
||
|
|
||
|
|
||
|
key_token = ben_dict_get_by_str(key_args, "token");
|
||
|
|
||
|
if ( key_token ) {
|
||
|
token_len = ben_str_len(key_token);
|
||
|
memcpy(token, ben_str_val(key_token), token_len);
|
||
|
}
|
||
|
|
||
|
key_port = ben_dict_get_by_str(key_args, "port");
|
||
|
|
||
|
if ( key_port ) {
|
||
|
port = ben_int_val(key_port);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
key_resp = ben_dict_get_by_str(msg_ben, "r");
|
||
|
if ( key_resp ) {
|
||
|
key_values = ben_dict_get_by_str(key_resp, "values");
|
||
|
|
||
|
if ( key_values ) {
|
||
|
values_len = ben_str_len(key_values);
|
||
|
memcpy(values, ben_str_val(key_values), values_len);
|
||
|
}
|
||
|
|
||
|
key_values6 = ben_dict_get_by_str(key_resp, "values6");
|
||
|
|
||
|
if ( key_values6 ) {
|
||
|
values6_len = ben_str_len(key_values6);
|
||
|
memcpy(values6, ben_str_val(key_values6), values6_len);
|
||
|
}
|
||
|
|
||
|
key_nodes = ben_dict_get_by_str(key_resp, "nodes");
|
||
|
|
||
|
if ( key_nodes ) {
|
||
|
nodes_len = ben_str_len(key_nodes);
|
||
|
memcpy(nodes, ben_str_val(key_nodes), nodes_len);
|
||
|
ks_log(KS_LOG_DEBUG, "Parsed nodes from response with length %d\n", nodes_len);
|
||
|
}
|
||
|
|
||
|
key_nodes6 = ben_dict_get_by_str(key_resp, "nodes6");
|
||
|
|
||
|
if ( key_nodes6 ) {
|
||
|
nodes6_len = ben_str_len(key_nodes6);
|
||
|
memcpy(nodes6, ben_str_val(key_nodes6), nodes6_len);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
logmsg = calloc(1, buflen);
|
||
|
ks_log(KS_LOG_DEBUG, "Message type %d\n", message);
|
||
|
switch(message) {
|
||
|
case DHT_MSG_STORE_PUT:
|
||
|
if ( buf ) {
|
||
|
struct ks_dht_store_entry_s *entry = NULL;
|
||
|
struct bencode *sig = NULL, *salt = NULL;
|
||
|
struct bencode *sig_ben = NULL, *pk_ben = NULL;
|
||
|
unsigned char *data_sig = NULL;
|
||
|
const char *sig_binary = NULL, *pk_binary = NULL;
|
||
|
size_t data_sig_len = 0;
|
||
|
|
||
|
/* Handle checking callback handler, and response */
|
||
|
if ( !key_args ) {
|
||
|
ks_log(KS_LOG_DEBUG, "Failed to locate 'a' field in message\n");
|
||
|
goto dontread;
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Successfully located 'a' field in message\n");
|
||
|
}
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Received bencode store PUT: \n\n%s\n", ben_print(msg_ben));
|
||
|
|
||
|
sig_ben = ben_dict_get_by_str(key_args, "sig");
|
||
|
sig_binary = ben_str_val(sig_ben);
|
||
|
|
||
|
pk_ben = ben_dict_get_by_str(key_args, "k");
|
||
|
pk_binary = ben_str_val(pk_ben);
|
||
|
|
||
|
sig = ben_dict();
|
||
|
|
||
|
salt = ben_dict_get_by_str(key_args, "salt");
|
||
|
if ( salt ) {
|
||
|
ben_dict_set(sig, ben_blob("salt", 4), ben_blob(ben_str_val(salt), ben_str_len(salt)));
|
||
|
}
|
||
|
|
||
|
/* TODO: fix double reference here. Need to bencode duplicate these values, and then free sig when finished encoding it */
|
||
|
ben_dict_set(sig, ben_blob("seq", 3), ben_dict_get_by_str(key_args, "seq"));
|
||
|
ben_dict_set(sig, ben_blob("v", 1), ben_dict_get_by_str(key_args, "v"));
|
||
|
|
||
|
data_sig = (unsigned char *) ben_encode(&data_sig_len, sig);
|
||
|
|
||
|
if ( !data_sig ) {
|
||
|
ks_log(KS_LOG_DEBUG, "Failed to encode message for signature validation\n");
|
||
|
goto dontread;
|
||
|
}
|
||
|
|
||
|
if (crypto_sign_verify_detached((unsigned char *)sig_binary, data_sig, data_sig_len, (unsigned char *) pk_binary) != 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Signature failed to verify. Corrupted or malicious data suspected!\n");
|
||
|
goto dontread;
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Valid message store signature.\n");
|
||
|
}
|
||
|
|
||
|
ks_dht_store_entry_create(h, msg_ben, &entry, 600, 0);
|
||
|
ks_dht_store_insert(h->store, entry, h->now);
|
||
|
}
|
||
|
break;
|
||
|
case DHT_MSG_INVALID:
|
||
|
case DHT_MSG_ERROR:
|
||
|
ks_log(KS_LOG_DEBUG, "Unparseable message: %s\n", debug_printable(buf, logmsg, buflen));
|
||
|
goto dontread;
|
||
|
case DHT_MSG_REPLY:
|
||
|
if (tid_len != 4) {
|
||
|
ks_log(KS_LOG_DEBUG, "Broken node truncates transaction ids: %s\n", debug_printable(buf, logmsg, buflen));
|
||
|
/* This is really annoying, as it means that we will
|
||
|
time-out all our searches that go through this node.
|
||
|
Kill it. */
|
||
|
blacklist_node(h, id, from);
|
||
|
goto dontread;
|
||
|
}
|
||
|
if (tid_match(tid, "pn", NULL)) {
|
||
|
ks_log(KS_LOG_DEBUG, "Pong!\n");
|
||
|
new_node(h, id, from, 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(h, ttid, from->family);
|
||
|
}
|
||
|
ks_log(KS_LOG_DEBUG, "Nodes found (%d+%d)%s!\n", nodes_len/26, nodes6_len/38, gp ? " for get_peers" : "");
|
||
|
if (nodes_len % 26 != 0 || nodes6_len % 38 != 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Unexpected length for node info!\n");
|
||
|
blacklist_node(h, id, from);
|
||
|
} else if (gp && sr == NULL) {
|
||
|
ks_log(KS_LOG_DEBUG, "Unknown search!\n");
|
||
|
new_node(h, id, from, 1);
|
||
|
} else {
|
||
|
int i;
|
||
|
new_node(h, id, from, 2);
|
||
|
for (i = 0; i < nodes_len / 26; i++) {
|
||
|
unsigned char *ni = nodes + i * 26;
|
||
|
struct sockaddr_in sin;
|
||
|
ks_sockaddr_t addr = { 0 };
|
||
|
|
||
|
if (id_cmp(ni, h->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);
|
||
|
|
||
|
ks_addr_set_raw(&addr, &sin.sin_addr, sin.sin_port, AF_INET);
|
||
|
new_node(h, ni, &addr, 0);
|
||
|
if (sr && sr->af == AF_INET) {
|
||
|
insert_search_node(h, ni, &addr, sr, 0, NULL, 0);
|
||
|
}
|
||
|
}
|
||
|
for (i = 0; i < nodes6_len / 38; i++) {
|
||
|
unsigned char *ni = nodes6 + i * 38;
|
||
|
struct sockaddr_in6 sin6;
|
||
|
ks_sockaddr_t addr = { 0 };
|
||
|
|
||
|
if (id_cmp(ni, h->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);
|
||
|
|
||
|
ks_addr_set_raw(&addr, &sin6.sin6_addr, sin6.sin6_port, AF_INET6);
|
||
|
|
||
|
new_node(h, ni, &addr, 0);
|
||
|
if (sr && sr->af == AF_INET6) {
|
||
|
insert_search_node(h, ni, &addr, 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(h, sr, NULL);
|
||
|
}
|
||
|
}
|
||
|
if (sr) {
|
||
|
if ( token_len ) {
|
||
|
ks_log(KS_LOG_DEBUG, "token %d [%.*s]\n", token_len, token);
|
||
|
}
|
||
|
insert_search_node(h, id, from, sr, 1, token, token_len);
|
||
|
if (values_len > 0 || values6_len > 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Got values (%d+%d)!\n", values_len / 6, values6_len / 18);
|
||
|
if (h->callback) {
|
||
|
if (values_len > 0) {
|
||
|
h->callback(h->closure, KS_DHT_EVENT_VALUES, sr->id, (void*)values, values_len);
|
||
|
}
|
||
|
if (values6_len > 0) {
|
||
|
h->callback(h->closure, KS_DHT_EVENT_VALUES6, sr->id, (void*)values6, values6_len);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else if (tid_match(tid, "ap", &ttid)) {
|
||
|
struct search *sr;
|
||
|
ks_log(KS_LOG_DEBUG, "Got reply to announce_peer.\n");
|
||
|
sr = find_search(h, ttid, from->family);
|
||
|
if (!sr) {
|
||
|
ks_log(KS_LOG_DEBUG, "Unknown search!\n");
|
||
|
new_node(h, id, from, 1);
|
||
|
} else {
|
||
|
int i;
|
||
|
new_node(h, id, from, 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 = h->now;
|
||
|
sr->nodes[i].acked = 1;
|
||
|
sr->nodes[i].pinged = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
/* See comment for gp above. */
|
||
|
search_send_get_peers(h, sr, NULL);
|
||
|
}
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Unexpected reply: %s\n", debug_printable(buf, logmsg, buflen));
|
||
|
}
|
||
|
break;
|
||
|
case DHT_MSG_PING:
|
||
|
ks_log(KS_LOG_DEBUG, "Ping (%d)!\n", tid_len);
|
||
|
new_node(h, id, from, 1);
|
||
|
ks_log(KS_LOG_DEBUG, "Sending pong.\n");
|
||
|
send_pong(h, from, tid, tid_len);
|
||
|
break;
|
||
|
case DHT_MSG_FIND_NODE:
|
||
|
if ( key_args ) {
|
||
|
/*
|
||
|
http://www.bittorrent.org/beps/bep_0005.html
|
||
|
http://www.bittorrent.org/beps/bep_0032.html
|
||
|
|
||
|
find_node Query = {"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456"}}
|
||
|
bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe
|
||
|
*/
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Find node!\n");
|
||
|
/* Needs to fetch the from, and fromlen from the decoded message, as well as the target and want */
|
||
|
new_node(h, id, from, 1);
|
||
|
ks_log(KS_LOG_DEBUG, "Sending closest nodes (%d).\n", want);
|
||
|
send_closest_nodes(h, from, tid, tid_len, target, want, 0, NULL, NULL, 0);
|
||
|
} else {
|
||
|
goto dontread;
|
||
|
}
|
||
|
break;
|
||
|
case DHT_MSG_GET_PEERS:
|
||
|
/*
|
||
|
http://www.bittorrent.org/beps/bep_0005.html
|
||
|
|
||
|
get_peers Query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456"}}
|
||
|
bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe
|
||
|
*/
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Get_peers!\n");
|
||
|
new_node(h, id, from, 1);
|
||
|
if (id_cmp(info_hash, zeroes) == 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Eek! Got get_peers with no info_hash.\n");
|
||
|
send_error(h, from, tid, tid_len, 203, "Get_peers with no info_hash");
|
||
|
break;
|
||
|
} else {
|
||
|
struct storage *st = find_storage(h, info_hash);
|
||
|
unsigned char token[TOKEN_SIZE];
|
||
|
make_token(h, from, 0, token);
|
||
|
if (st && st->numpeers > 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Sending found%s peers.\n", from->family == AF_INET6 ? " IPv6" : "");
|
||
|
send_closest_nodes(h, from, tid, tid_len, info_hash, want, from->family, st, token, TOKEN_SIZE);
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Sending nodes for get_peers.\n");
|
||
|
send_closest_nodes(h, from, tid, tid_len, info_hash, want, 0, NULL, token, TOKEN_SIZE);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case DHT_MSG_ANNOUNCE_PEER:
|
||
|
ks_log(KS_LOG_DEBUG, "Announce peer!\n");
|
||
|
new_node(h, id, from, 1);
|
||
|
if (id_cmp(info_hash, zeroes) == 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Announce_peer with no info_hash.\n");
|
||
|
send_error(h, from, tid, tid_len, 203, "Announce_peer with no info_hash");
|
||
|
break;
|
||
|
}
|
||
|
if (!token_match(h, token, token_len, from)) {
|
||
|
ks_log(KS_LOG_DEBUG, "Incorrect token for announce_peer.\n");
|
||
|
send_error(h, from, tid, tid_len, 203, "Announce_peer with wrong token");
|
||
|
break;
|
||
|
}
|
||
|
if (port == 0) {
|
||
|
ks_log(KS_LOG_DEBUG, "Announce_peer with forbidden port %d.\n", port);
|
||
|
send_error(h, from, tid, tid_len, 203, "Announce_peer with forbidden port number");
|
||
|
break;
|
||
|
}
|
||
|
storage_store(h, info_hash, from, port);
|
||
|
/* Note that if storage_store failed, we lie to the requestor. This is to prevent them from backtracking, and hence polluting the DHT. */
|
||
|
ks_log(KS_LOG_DEBUG, "Sending peer announced.\n");
|
||
|
send_peer_announced(h, from, tid, tid_len);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dontread:
|
||
|
if (h->now >= h->rotate_secrets_time) {
|
||
|
rotate_secrets(h);
|
||
|
}
|
||
|
|
||
|
if (h->now >= h->expire_stuff_time) {
|
||
|
expire_buckets(h, h->buckets);
|
||
|
expire_buckets(h, h->buckets6);
|
||
|
expire_storage(h);
|
||
|
expire_searches(h);
|
||
|
}
|
||
|
|
||
|
if (h->search_time > 0 && h->now >= h->search_time) {
|
||
|
struct search *sr;
|
||
|
sr = h->searches;
|
||
|
while (sr) {
|
||
|
if (!sr->done && sr->step_time + 5 <= h->now) {
|
||
|
search_step(h, sr);
|
||
|
}
|
||
|
sr = sr->next;
|
||
|
}
|
||
|
|
||
|
h->search_time = 0;
|
||
|
|
||
|
sr = h->searches;
|
||
|
while (sr) {
|
||
|
if (!sr->done) {
|
||
|
time_t tm = sr->step_time + 15 + random() % 10;
|
||
|
if (h->search_time == 0 || h->search_time > tm) {
|
||
|
h->search_time = tm;
|
||
|
}
|
||
|
}
|
||
|
sr = sr->next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (h->now >= h->confirm_nodes_time) {
|
||
|
int soon = 0;
|
||
|
|
||
|
soon |= bucket_maintenance(h, AF_INET);
|
||
|
soon |= bucket_maintenance(h, AF_INET6);
|
||
|
|
||
|
if (!soon) {
|
||
|
if (h->mybucket_grow_time >= h->now - 150) {
|
||
|
soon |= neighbourhood_maintenance(h, AF_INET);
|
||
|
}
|
||
|
if (h->mybucket6_grow_time >= h->now - 150) {
|
||
|
soon |= neighbourhood_maintenance(h, AF_INET6);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* In order to maintain all buckets' age within 600 seconds, worst case is roughly 27 seconds, assuming the table is 22 bits deep.
|
||
|
We want to keep a margin for neighborhood maintenance, so keep this within 25 seconds. */
|
||
|
if (soon) {
|
||
|
h->confirm_nodes_time = h->now + 5 + random() % 20;
|
||
|
} else {
|
||
|
h->confirm_nodes_time = h->now + 60 + random() % 120;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (h->confirm_nodes_time > h->now) {
|
||
|
h->tosleep = h->confirm_nodes_time - h->now;
|
||
|
} else {
|
||
|
h->tosleep = 0;
|
||
|
}
|
||
|
|
||
|
if (h->search_time > 0) {
|
||
|
if (h->search_time <= h->now) {
|
||
|
h->tosleep = 0;
|
||
|
} else if (h->tosleep > h->search_time - h->now) {
|
||
|
h->tosleep = h->search_time - h->now;
|
||
|
}
|
||
|
}
|
||
|
ks_safe_free(logmsg);
|
||
|
|
||
|
ks_dht_store_prune(h->store, h->now);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) dht_get_nodes(dht_handle_t *h, struct sockaddr_in *sin, int *num,
|
||
|
struct sockaddr_in6 *sin6, int *num6)
|
||
|
{
|
||
|
int i, j;
|
||
|
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(h, h->myid, AF_INET);
|
||
|
if (b == NULL) {
|
||
|
goto no_ipv4;
|
||
|
}
|
||
|
|
||
|
n = b->nodes;
|
||
|
while (n && i < *num) {
|
||
|
if (node_good(h, n)) {
|
||
|
sin[i] = *(struct sockaddr_in*)&n->ss;
|
||
|
i++;
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
|
||
|
b = h->buckets;
|
||
|
while (b && i < *num) {
|
||
|
if (!in_bucket(h->myid, b)) {
|
||
|
n = b->nodes;
|
||
|
while (n && i < *num) {
|
||
|
if (node_good(h, n)) {
|
||
|
sin[i] = *(struct sockaddr_in*)&n->ss;
|
||
|
i++;
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
}
|
||
|
b = b->next;
|
||
|
}
|
||
|
|
||
|
no_ipv4:
|
||
|
|
||
|
j = 0;
|
||
|
|
||
|
b = find_bucket(h, h->myid, AF_INET6);
|
||
|
if (b == NULL) {
|
||
|
goto no_ipv6;
|
||
|
}
|
||
|
|
||
|
n = b->nodes;
|
||
|
while (n && j < *num6) {
|
||
|
if (node_good(h, n)) {
|
||
|
sin6[j] = *(struct sockaddr_in6*)&n->ss;
|
||
|
j++;
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
|
||
|
b = h->buckets6;
|
||
|
while (b && j < *num6) {
|
||
|
if (!in_bucket(h->myid, b)) {
|
||
|
n = b->nodes;
|
||
|
while (n && j < *num6) {
|
||
|
if (node_good(h, n)) {
|
||
|
sin6[j] = *(struct sockaddr_in6*)&n->ss;
|
||
|
j++;
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
}
|
||
|
b = b->next;
|
||
|
}
|
||
|
|
||
|
no_ipv6:
|
||
|
|
||
|
*num = i;
|
||
|
*num6 = j;
|
||
|
return i + j;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) dht_insert_node(dht_handle_t *h, const unsigned char *id, ks_sockaddr_t *sa)
|
||
|
{
|
||
|
struct node *n;
|
||
|
|
||
|
if (sa->family != AF_INET) {
|
||
|
errno = EAFNOSUPPORT;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
n = new_node(h, id, sa, 0);
|
||
|
return !!n;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) dht_ping_node(dht_handle_t *h, ks_sockaddr_t *sa)
|
||
|
{
|
||
|
unsigned char tid[4];
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Sending ping.\n");
|
||
|
make_tid(tid, "pn", 0);
|
||
|
return send_ping(h, sa, 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); \
|
||
|
offset += delta;
|
||
|
|
||
|
#define ADD_V(buf, offset, size) \
|
||
|
if (h->have_v) { \
|
||
|
COPY(buf, offset, h->my_v, sizeof(h->my_v), size); \
|
||
|
}
|
||
|
|
||
|
static int dht_send(dht_handle_t *h, const void *buf, size_t len, int flags, const ks_sockaddr_t *sa)
|
||
|
{
|
||
|
char ip[80] = "";
|
||
|
ks_ip_t *ipt;
|
||
|
|
||
|
if (node_blacklisted(h, sa)) {
|
||
|
ks_log(KS_LOG_DEBUG, "Attempting to send to blacklisted node.\n");
|
||
|
errno = EPERM;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
ks_ip_route(ip, sizeof(ip), sa->host);
|
||
|
|
||
|
if (!(ipt = ks_hash_search(h->iphash, ip, KS_UNLOCKED)) && h->autoroute) {
|
||
|
ipt = add_ip(h, ip, 0, sa->family);
|
||
|
}
|
||
|
|
||
|
if (!ipt) {
|
||
|
ks_log(KS_LOG_ERROR, "No route to dest\n");
|
||
|
errno = EINVAL;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ks_log(KS_LOG_INFO, "Sending message to [%s] port (%d)\n", sa->host, sa->port);
|
||
|
|
||
|
if (ks_socket_sendto(ipt->sock, (void *)buf, &len, (ks_sockaddr_t *)sa) != KS_STATUS_SUCCESS) {
|
||
|
ks_log(KS_LOG_ERROR, "Socket Error (%s)\n", strerror(errno));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Sample ping packet '{"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}}' */
|
||
|
/* http://www.bittorrent.org/beps/bep_0005.html */
|
||
|
static int send_ping(dht_handle_t *h, const ks_sockaddr_t *sa, const unsigned char *tid, int tid_len)
|
||
|
{
|
||
|
char buf[512];
|
||
|
int i = 0;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *bencode_a_p = ben_dict();
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("q", 1));
|
||
|
ben_dict_set(bencode_p, ben_blob("q", 1), ben_blob("ping", 4));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("id", 2), ben_blob(h->myid, 20));
|
||
|
ben_dict_set(bencode_p, ben_blob("a", 1), bencode_a_p);
|
||
|
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p); /* This SHOULD free the bencode_a_p as well */
|
||
|
|
||
|
return dht_send(h, buf, i, 0, sa);
|
||
|
}
|
||
|
|
||
|
/* Sample pong packet '{"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}' */
|
||
|
/* http://www.bittorrent.org/beps/bep_0005.html */
|
||
|
static int send_pong(dht_handle_t *h, const ks_sockaddr_t *sa, const unsigned char *tid, int tid_len)
|
||
|
{
|
||
|
char buf[512];
|
||
|
int i = 0;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *bencode_a_p = ben_dict();
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("r", 1));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("id", 2), ben_blob(h->myid, 20));
|
||
|
ben_dict_set(bencode_p, ben_blob("r", 1), bencode_a_p);
|
||
|
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p); /* This SHOULD free the bencode_a_p as well */
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Encoded PONG\n");
|
||
|
return dht_send(h, buf, i, 0, sa);
|
||
|
}
|
||
|
|
||
|
/* Sample find_node packet '{"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456"}}' */
|
||
|
/* Sample find_node packet w/ want '{"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456", "want":"n4"}}' */
|
||
|
/* http://www.bittorrent.org/beps/bep_0005.html */
|
||
|
/* http://www.bittorrent.org/beps/bep_0032.html for want parameter */
|
||
|
static int send_find_node(dht_handle_t *h, const ks_sockaddr_t *sa,
|
||
|
const unsigned char *tid, int tid_len,
|
||
|
const unsigned char *target, int target_len, int want, int confirm)
|
||
|
{
|
||
|
char buf[512];
|
||
|
int i = 0;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *bencode_a_p = ben_dict();
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("q", 1));
|
||
|
ben_dict_set(bencode_p, ben_blob("q", 1), ben_blob("find_node", 9));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("id", 2), ben_blob(h->myid, 20));
|
||
|
|
||
|
if (target) ben_dict_set(bencode_a_p, ben_blob("target", 6), ben_blob(target, target_len));
|
||
|
|
||
|
if (want > 0) {
|
||
|
struct bencode *bencode_w = ben_list();
|
||
|
if (want & WANT4) {
|
||
|
ben_list_append(bencode_w, ben_blob("n4", 2));
|
||
|
}
|
||
|
if (want & WANT6) {
|
||
|
ben_list_append(bencode_w, ben_blob("n6", 2));
|
||
|
}
|
||
|
ben_dict_set(bencode_a_p, ben_blob("want", 4), bencode_w);
|
||
|
}
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("a", 1), bencode_a_p);
|
||
|
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p); /* This SHOULD free the bencode_a_p as well */
|
||
|
|
||
|
return dht_send(h, buf, i, confirm ? MSG_CONFIRM : 0, sa);
|
||
|
}
|
||
|
|
||
|
/* sample find_node response '{"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}}'*/
|
||
|
/* http://www.bittorrent.org/beps/bep_0005.html */
|
||
|
static int send_nodes_peers(dht_handle_t *h, const ks_sockaddr_t *sa,
|
||
|
const unsigned char *tid, int tid_len,
|
||
|
const unsigned char *nodes, int nodes_len,
|
||
|
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, j0, j, k;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *bencode_a_p = ben_dict();
|
||
|
struct bencode *ben_array = ben_list();
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("r", 1));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("id", 2), ben_blob(h->myid, 20));
|
||
|
if (token_len) ben_dict_set(bencode_a_p, ben_blob("token", 5), ben_blob(token, token_len));
|
||
|
if (nodes_len) ben_dict_set(bencode_a_p, ben_blob("nodes", 5), ben_blob(nodes, nodes_len));
|
||
|
if (nodes6_len) ben_dict_set(bencode_a_p, ben_blob("nodes6", 6), ben_blob(nodes6, nodes6_len));
|
||
|
|
||
|
/*
|
||
|
Response with peers = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "values": ["axje.u", "idhtnm"]}}
|
||
|
*/
|
||
|
|
||
|
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.
|
||
|
|
||
|
j0 = random() % st->numpeers;
|
||
|
j = j0;
|
||
|
k = 0;
|
||
|
|
||
|
do {
|
||
|
if (st->peers[j].addr.family == af) {
|
||
|
char data[18];
|
||
|
unsigned short swapped = htons(st->peers[j].addr.port);
|
||
|
void *ip = NULL;
|
||
|
ks_size_t iplen = 0;
|
||
|
|
||
|
ks_addr_raw_data(&st->peers[j].addr, &ip, &iplen);
|
||
|
|
||
|
memcpy(data, ip, iplen);
|
||
|
memcpy(data + iplen, &swapped, 2);
|
||
|
ben_list_append(ben_array, ben_blob(data, iplen + 2));
|
||
|
k++;
|
||
|
}
|
||
|
j = (j + 1) % st->numpeers;
|
||
|
} while(j != j0 && k < 50);
|
||
|
ben_dict_set(bencode_a_p, ben_blob("values", 6), ben_array);
|
||
|
}
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("r", 1), bencode_a_p);
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p); /* This SHOULD free the bencode_a_p as well */
|
||
|
|
||
|
return dht_send(h, buf, i, 0, sa);
|
||
|
}
|
||
|
|
||
|
static int insert_closest_node(unsigned char *nodes, int numnodes,
|
||
|
const unsigned char *id, struct node *n)
|
||
|
{
|
||
|
int i, size;
|
||
|
|
||
|
if (n->ss.family == AF_INET) {
|
||
|
size = 26;
|
||
|
} else if (n->ss.family == AF_INET6) {
|
||
|
size = 38;
|
||
|
} else {
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
for (i = 0; i< numnodes; i++) {
|
||
|
if (id_cmp(n->id, nodes + size * i) == 0) {
|
||
|
return numnodes;
|
||
|
}
|
||
|
if (xorcmp(n->id, nodes + size * i, id) < 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i == 8) {
|
||
|
return numnodes;
|
||
|
}
|
||
|
|
||
|
if (numnodes < 8) {
|
||
|
numnodes++;
|
||
|
}
|
||
|
|
||
|
if (i < numnodes - 1) {
|
||
|
memmove(nodes + size * (i + 1), nodes + size * i, size * (numnodes - i - 1));
|
||
|
}
|
||
|
|
||
|
if (n->ss.family == AF_INET) {
|
||
|
memcpy(nodes + size * i, n->id, 20);
|
||
|
memcpy(nodes + size * i + 20, &n->ss.v.v4.sin_addr, 4);
|
||
|
memcpy(nodes + size * i + 24, &n->ss.v.v4.sin_port, 2);
|
||
|
} else if (n->ss.family == AF_INET6) {
|
||
|
memcpy(nodes + size * i, n->id, 20);
|
||
|
memcpy(nodes + size * i + 20, &n->ss.v.v6.sin6_addr, 16);
|
||
|
memcpy(nodes + size * i + 36, &n->ss.v.v6.sin6_port, 2);
|
||
|
} else {
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
return numnodes;
|
||
|
}
|
||
|
|
||
|
static int buffer_closest_nodes(dht_handle_t *h, unsigned char *nodes, int numnodes, const unsigned char *id, struct bucket *b)
|
||
|
{
|
||
|
struct node *n = b->nodes;
|
||
|
while (n) {
|
||
|
if (node_good(h, n)) {
|
||
|
numnodes = insert_closest_node(nodes, numnodes, id, n);
|
||
|
}
|
||
|
n = n->next;
|
||
|
}
|
||
|
return numnodes;
|
||
|
}
|
||
|
|
||
|
static int send_closest_nodes(dht_handle_t *h, const ks_sockaddr_t *sa,
|
||
|
const unsigned char *tid, int tid_len,
|
||
|
const unsigned char *id, int want,
|
||
|
int af, struct storage *st,
|
||
|
const unsigned char *token, int token_len)
|
||
|
{
|
||
|
unsigned char nodes[8 * 26];
|
||
|
unsigned char nodes6[8 * 38];
|
||
|
int numnodes = 0, numnodes6 = 0;
|
||
|
struct bucket *b;
|
||
|
|
||
|
if (want < 0) {
|
||
|
want = sa->family == AF_INET ? WANT4 : WANT6;
|
||
|
}
|
||
|
|
||
|
if ((want & WANT4)) {
|
||
|
if ((b = find_bucket(h, id, AF_INET))) {
|
||
|
numnodes = buffer_closest_nodes(h, nodes, numnodes, id, b);
|
||
|
if (b->next) {
|
||
|
numnodes = buffer_closest_nodes(h, nodes, numnodes, id, b->next);
|
||
|
}
|
||
|
if ((b = previous_bucket(h, b))) {
|
||
|
numnodes = buffer_closest_nodes(h, nodes, numnodes, id, b);
|
||
|
}
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "send_closest_nodes did not find a 'close' ipv4 bucket\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((want & WANT6)) {
|
||
|
if ((b = find_bucket(h, id, AF_INET6))) {
|
||
|
numnodes6 = buffer_closest_nodes(h, nodes6, numnodes6, id, b);
|
||
|
if (b->next) {
|
||
|
numnodes6 = buffer_closest_nodes(h, nodes6, numnodes6, id, b->next);
|
||
|
}
|
||
|
if ((b = previous_bucket(h, b))) {
|
||
|
numnodes6 = buffer_closest_nodes(h, nodes6, numnodes6, id, b);
|
||
|
}
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "send_closest_nodes did not find a 'close' ipv6 bucket\n");
|
||
|
}
|
||
|
}
|
||
|
ks_log(KS_LOG_DEBUG, "send_closest_nodes (%d+%d nodes.)\n", numnodes, numnodes6);
|
||
|
|
||
|
return send_nodes_peers(h, sa, tid, tid_len,
|
||
|
nodes, numnodes * 26,
|
||
|
nodes6, numnodes6 * 38,
|
||
|
af, st, token, token_len);
|
||
|
}
|
||
|
|
||
|
/* sample get_peers request '{"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456"}}'*/
|
||
|
/* sample get_peers w/ want '{"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456": "want":"n4"}}'*/
|
||
|
/* http://www.bittorrent.org/beps/bep_0005.html */
|
||
|
/* http://www.bittorrent.org/beps/bep_0032.html for want parameter */
|
||
|
static int send_get_peers(dht_handle_t *h, const ks_sockaddr_t *sa,
|
||
|
unsigned char *tid, int tid_len, unsigned char *infohash,
|
||
|
int want, int confirm)
|
||
|
{
|
||
|
char buf[512];
|
||
|
int i = 0;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *bencode_a_p = ben_dict();
|
||
|
int infohash_len = infohash ? strlen((const char*)infohash) : 0;
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("q", 1));
|
||
|
ben_dict_set(bencode_p, ben_blob("q", 1), ben_blob("get_peers", 9));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("id", 2), ben_blob(h->myid, 20));
|
||
|
if (want > 0) {
|
||
|
struct bencode *bencode_w_p = ben_list();
|
||
|
if (want & WANT4) {
|
||
|
ben_list_append(bencode_w_p, ben_blob("n4", 2));
|
||
|
}
|
||
|
if (want & WANT6) {
|
||
|
ben_list_append(bencode_w_p, ben_blob("n6", 2));
|
||
|
}
|
||
|
ben_dict_set(bencode_a_p, ben_blob("want", 4), bencode_w_p);
|
||
|
}
|
||
|
ben_dict_set(bencode_a_p, ben_blob("info_hash", 9), ben_blob(infohash, infohash_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("a", 1), bencode_a_p);
|
||
|
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p); /* This SHOULD free the bencode_a_p as well */
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Encoded GET_PEERS\n");
|
||
|
|
||
|
return dht_send(h, buf, i, confirm ? MSG_CONFIRM : 0, sa);
|
||
|
}
|
||
|
/* '{"t":"aa", "y":"q", "q":"announce_peer", "a": {"id":"abcdefghij0123456789", "implied_port": 1, "info_hash":"mnopqrstuvwxyz123456", "port": 6881, "token": "aoeusnth"}}'*/
|
||
|
static int send_announce_peer(dht_handle_t *h, const ks_sockaddr_t *sa,
|
||
|
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;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *bencode_a_p = ben_dict();
|
||
|
int infohash_len = infohash ? strlen((const char*)infohash) : 0;
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("q", 1));
|
||
|
ben_dict_set(bencode_p, ben_blob("q", 1), ben_blob("announce_peer", 13));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("id", 2), ben_blob(h->myid, 20));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("info_hash", 9), ben_blob(infohash, infohash_len));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("port", 5), ben_int(port));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("token", 5), ben_blob(token, token_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("a", 1), bencode_a_p);
|
||
|
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p); /* This SHOULD free the bencode_a_p as well */
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Encoded ANNOUNCE_PEERS\n");
|
||
|
return dht_send(h, buf, i, confirm ? MSG_CONFIRM : 0, sa);
|
||
|
}
|
||
|
/* '{"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}'*/
|
||
|
static int send_peer_announced(dht_handle_t *h, const ks_sockaddr_t *sa, unsigned char *tid, int tid_len)
|
||
|
{
|
||
|
char buf[512];
|
||
|
int i = 0;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *bencode_a_p = ben_dict();
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("r", 1));
|
||
|
ben_dict_set(bencode_a_p, ben_blob("id", 2), ben_blob(h->myid, 20));
|
||
|
ben_dict_set(bencode_p, ben_blob("r", 1), bencode_a_p);
|
||
|
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p); /* This SHOULD free the bencode_a_p as well */
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Encoded peer_announced: %s\n\n", buf);
|
||
|
return dht_send(h, buf, i, 0, sa);
|
||
|
}
|
||
|
|
||
|
/* '{"t":"aa", "y":"e", "e":[201, "A Generic Error Ocurred"]}'*/
|
||
|
static int send_error(dht_handle_t *h, const ks_sockaddr_t *sa,
|
||
|
unsigned char *tid, int tid_len,
|
||
|
int code, const char *message)
|
||
|
{
|
||
|
char buf[512];
|
||
|
int i = 0;
|
||
|
struct bencode *bencode_p = ben_dict();
|
||
|
struct bencode *ben_array = ben_list();
|
||
|
|
||
|
ben_dict_set(bencode_p, ben_blob("t", 1), ben_blob(tid, tid_len));
|
||
|
ben_dict_set(bencode_p, ben_blob("y", 1), ben_blob("e", 1));
|
||
|
ben_list_append(ben_array, ben_int(code));
|
||
|
ben_list_append(ben_array, ben_blob(message, strlen(message)));
|
||
|
ben_dict_set(bencode_p, ben_blob("e", 1), ben_array);
|
||
|
|
||
|
i = ben_encode2(buf, 512, bencode_p);
|
||
|
ben_free(bencode_p);
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "Encoded error: %s\n\n", buf);
|
||
|
return dht_send(h, buf, i, 0, sa);
|
||
|
}
|
||
|
|
||
|
#undef CHECK
|
||
|
#undef INC
|
||
|
#undef COPY
|
||
|
#undef ADD_V
|
||
|
|
||
|
/*
|
||
|
|
||
|
#ifdef HAVE_MEMMEM
|
||
|
|
||
|
static void *dht_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen)
|
||
|
{
|
||
|
return memmem(haystack, haystacklen, needle, needlelen);
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
static void *dht_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen)
|
||
|
{
|
||
|
const char *h = haystack;
|
||
|
const char *n = needle;
|
||
|
size_t i;
|
||
|
|
||
|
|
||
|
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 dht_msg_type_t parse_message(struct bencode *bencode_p,
|
||
|
unsigned char *tid_return, int *tid_len,
|
||
|
unsigned char *id_return)
|
||
|
{
|
||
|
// const unsigned char *p;
|
||
|
dht_msg_type_t type = DHT_MSG_INVALID;
|
||
|
struct bencode *b_tmp = NULL;
|
||
|
struct bencode *key_t = ben_dict_get_by_str(bencode_p, "t");
|
||
|
struct bencode *key_args = ben_dict_get_by_str(bencode_p, "a");
|
||
|
struct bencode *key_resp = ben_dict_get_by_str(bencode_p, "r");
|
||
|
|
||
|
/* Need to set tid, tid_len, and id_return. Then return the message type or msg_error. */
|
||
|
|
||
|
if ( key_t ) {
|
||
|
const char *tran = ben_str_val(key_t);
|
||
|
int tran_len = ben_str_len(key_t);
|
||
|
|
||
|
memcpy(tid_return, tran, (size_t) tran_len);
|
||
|
*tid_len = tran_len;
|
||
|
}
|
||
|
|
||
|
if ( key_args ) {
|
||
|
struct bencode *b_id = ben_dict_get_by_str( key_args, "id");
|
||
|
const char *id = b_id ? ben_str_val(b_id) : NULL;
|
||
|
int id_len = ben_str_len(b_id);
|
||
|
|
||
|
if ( id ) {
|
||
|
memcpy(id_return, id, id_len);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( key_resp ) {
|
||
|
struct bencode *b_id = ben_dict_get_by_str( key_resp, "id");
|
||
|
const char *id = b_id ? ben_str_val(b_id) : NULL;
|
||
|
int id_len = ben_str_len(b_id);
|
||
|
|
||
|
if ( id ) {
|
||
|
memcpy(id_return, id, id_len);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( ben_dict_get_by_str(bencode_p, "y") && key_t ){
|
||
|
/* This message is a KRPC message(aka DHT message) */
|
||
|
|
||
|
if ( ( b_tmp = ben_dict_get_by_str(bencode_p, "y") ) ) {
|
||
|
if ( !ben_cmp_with_str(b_tmp, "q") ) { /* Inbound queries */
|
||
|
struct bencode *b_query = NULL;
|
||
|
const char *val = ben_str_val(b_tmp);
|
||
|
ks_log(KS_LOG_DEBUG, "Message Query [%s]\n", val);
|
||
|
|
||
|
if ( !( b_query = ben_dict_get_by_str(bencode_p, "q") ) ) {
|
||
|
ks_log(KS_LOG_DEBUG, "Unable to locate query type field\n");
|
||
|
} else { /* Has a query type */
|
||
|
const char *query_type = ben_str_val(b_query);
|
||
|
if (!ben_cmp_with_str(b_query, "get_peers")) {
|
||
|
/*
|
||
|
{
|
||
|
'a': {
|
||
|
'id': '~\x12*\xe6L3\xba\x83\xafT\xe3\x02\x93\x0e\xae\xbd\xf8\xe1\x98\x87',
|
||
|
'info_hash': 'w"E\x85\xdd97\xd1\xfe\x13Q\xfa\xdae\x9d\x8f\x86\xddN9'
|
||
|
},
|
||
|
'q': 'get_peers',
|
||
|
't': '?\xf1',
|
||
|
'v': 'LT\x01\x00',
|
||
|
'y': 'q'
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
ks_log(KS_LOG_DEBUG, "get_peers query recieved\n");
|
||
|
type = DHT_MSG_GET_PEERS;
|
||
|
goto done;
|
||
|
} else if (!ben_cmp_with_str(b_query, "ping")) {
|
||
|
/*
|
||
|
{'a': {
|
||
|
'id': 'T\x1cd2\xc1\x85\xf4>?\x84#\xa8)\xd0`\x19y\xcf;\xda'
|
||
|
},
|
||
|
'q': 'ping',
|
||
|
't': 'pn\x00\x00',
|
||
|
'v': 'JC\x00\x00',
|
||
|
'y': 'q'
|
||
|
}
|
||
|
*/
|
||
|
ks_log(KS_LOG_DEBUG, "ping query recieved from client \n");
|
||
|
type = DHT_MSG_PING;
|
||
|
goto done;
|
||
|
} else if (!ben_cmp_with_str(b_query, "find_node")) {
|
||
|
/*
|
||
|
{'a': {
|
||
|
'id': 'T\x1cq\x7f\xa9^\xf2\x97S\xceE\xad\xc9S\x9b\xa1\x1cCX\x8d',
|
||
|
'target': 'T\x1cq\x7f\xa9C{\x83\xf9\xf6i&\x8b\x87*\xa2\xad\xad\x1a\xdd'
|
||
|
},
|
||
|
'q': 'find_node',
|
||
|
't': '\x915\xbe\xfb',
|
||
|
'v': 'UTu\x13',
|
||
|
'y': 'q'
|
||
|
}
|
||
|
*/
|
||
|
type = DHT_MSG_FIND_NODE;
|
||
|
goto done;
|
||
|
} else if (!ben_cmp_with_str(b_query, "put")) {
|
||
|
ks_log(KS_LOG_DEBUG, "Recieved a store put request\n");
|
||
|
type = DHT_MSG_STORE_PUT;
|
||
|
goto done;
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Unknown query type field [%s]\n", query_type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else if ( !ben_cmp_with_str(b_tmp, "r") ) { /* Responses */
|
||
|
const char *val = ben_str_val(b_tmp);
|
||
|
ks_log(KS_LOG_DEBUG, "Message Response [%s]\n", val);
|
||
|
type = DHT_MSG_REPLY;
|
||
|
goto done;
|
||
|
} else if ( !ben_cmp_with_str(b_tmp, "e") ) {
|
||
|
const char *val = ben_str_val(b_tmp);
|
||
|
ks_log(KS_LOG_DEBUG, "Message Error [%s]\n", val);
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Message Type Unknown!!!\n");
|
||
|
}
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Message Type Unknown, has no 'y' key!!!\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Decode the request or response
|
||
|
(b_tmp = ben_dict_get_by_str(bencode_p, "y"))) {
|
||
|
ks_log(KS_LOG_DEBUG, "query value: %s\n", ben_print(b_tmp));
|
||
|
*/
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Message not a remote DHT request nor query\n");
|
||
|
}
|
||
|
|
||
|
/* Default to MSG ERROR */
|
||
|
ks_log(KS_LOG_DEBUG, "Unknown or unsupported message type\n");
|
||
|
return type;
|
||
|
|
||
|
done:
|
||
|
return type;
|
||
|
|
||
|
/*
|
||
|
if (dht_memmem(buf, buflen, "1:q4:ping", 9)) {
|
||
|
return DHT_MSG_PING;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:q9:find_node", 14)) {
|
||
|
return DHT_MSG_FIND_NODE;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:q9:get_peers", 14)) {
|
||
|
return DHT_MSG_GET_PEERS;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:q13:announce_peer", 19)) {
|
||
|
return DHT_MSG_ANNOUNCE_PEER;
|
||
|
}
|
||
|
|
||
|
char *val = ben_str_val(b_tmp);
|
||
|
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
if (tid_return) {
|
||
|
p = dht_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 = dht_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 = dht_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 = dht_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 = dht_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 = dht_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_len) {
|
||
|
p = dht_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 (nodes6_len) {
|
||
|
p = dht_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 = dht_memmem(buf, buflen, "6:valuesl", 9);
|
||
|
if (p) {
|
||
|
int i = p - buf + 9;
|
||
|
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);
|
||
|
i = q + 1 + l - (char*)buf;
|
||
|
if (l == 6) {
|
||
|
if (j + l > *values_len) {
|
||
|
continue;
|
||
|
}
|
||
|
memcpy((char*)values_return + j, q + 1, l);
|
||
|
j += l;
|
||
|
} else if (l == 18) {
|
||
|
if (j6 + l > *values6_len) {
|
||
|
continue;
|
||
|
}
|
||
|
memcpy((char*)values6_return + j6, q + 1, l);
|
||
|
j6 += l;
|
||
|
} else {
|
||
|
ks_log(KS_LOG_DEBUG, "Received weird value -- %d bytes.\n", (int)l);
|
||
|
}
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (i >= buflen || buf[i] != 'e') {
|
||
|
ks_log(KS_LOG_DEBUG, "eek... unexpected end for values.\n");
|
||
|
}
|
||
|
if (values_len) {
|
||
|
*values_len = j;
|
||
|
}
|
||
|
if (values6_len) {
|
||
|
*values6_len = j6;
|
||
|
}
|
||
|
} else {
|
||
|
if (values_len) {
|
||
|
*values_len = 0;
|
||
|
}
|
||
|
if (values6_len) {
|
||
|
*values6_len = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (want_return) {
|
||
|
p = dht_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 {
|
||
|
ks_log(KS_LOG_DEBUG, "eek... unexpected want flag (%c)\n", buf[i]);
|
||
|
}
|
||
|
i += 2 + buf[i] - '0';
|
||
|
}
|
||
|
if (i >= buflen || buf[i] != 'e') {
|
||
|
ks_log(KS_LOG_DEBUG, "eek... unexpected end for want.\n");
|
||
|
}
|
||
|
} else {
|
||
|
*want_return = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#undef CHECK
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:y1:r", 6)) {
|
||
|
return DHT_MSG_REPLY;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:y1:e", 6)) {
|
||
|
return DHT_MSG_ERROR;
|
||
|
}
|
||
|
|
||
|
if (!dht_memmem(buf, buflen, "1:y1:q", 6)) {
|
||
|
return DHT_MSG_INVALID;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:q4:ping", 9)) {
|
||
|
return DHT_MSG_PING;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:q9:find_node", 14)) {
|
||
|
return DHT_MSG_FIND_NODE;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:q9:get_peers", 14)) {
|
||
|
return DHT_MSG_GET_PEERS;
|
||
|
}
|
||
|
|
||
|
if (dht_memmem(buf, buflen, "1:q13:announce_peer", 19)) {
|
||
|
return DHT_MSG_ANNOUNCE_PEER;
|
||
|
}
|
||
|
|
||
|
return DHT_MSG_INVALID;
|
||
|
|
||
|
overflow:
|
||
|
ks_log(KS_LOG_DEBUG, "Truncated message.\n");
|
||
|
return DHT_MSG_INVALID;
|
||
|
*/
|
||
|
|
||
|
}
|
||
|
|
||
|
/* b64encode function taken from kws.c. Maybe worth exposing a function like this. */
|
||
|
static const char c64[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||
|
static int b64encode(unsigned char *in, ks_size_t ilen, unsigned char *out, ks_size_t olen)
|
||
|
{
|
||
|
int y=0,bytes=0;
|
||
|
ks_size_t x=0;
|
||
|
unsigned int b=0,l=0;
|
||
|
|
||
|
if(olen) {
|
||
|
}
|
||
|
|
||
|
for(x=0;x<ilen;x++) {
|
||
|
b = (b<<8) + in[x];
|
||
|
l += 8;
|
||
|
while (l >= 6) {
|
||
|
out[bytes++] = c64[(b>>(l-=6))%64];
|
||
|
if(++y!=72) {
|
||
|
continue;
|
||
|
}
|
||
|
//out[bytes++] = '\n';
|
||
|
y=0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (l > 0) {
|
||
|
out[bytes++] = c64[((b%16)<<(6-l))%64];
|
||
|
}
|
||
|
if (l != 0) while (l < 6) {
|
||
|
out[bytes++] = '=', l += 2;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
This function should generate the fields needed for the mutable message.
|
||
|
|
||
|
Save the sending for another api, and possibly a third to generate and send all in one.
|
||
|
NOTE:
|
||
|
1. When sending a mutable message, CAS(compare and swap) values need to be validated.
|
||
|
2. Mutable messages MUST have a new key pair generated for each different mutable message.
|
||
|
The announce key is generated as a hash from the public key. To use one key pair for multiple messages,
|
||
|
a salt MUST be used that is unique and constant per message.
|
||
|
3. The target hash will be generated here, and will be the hash that must be used for announcing the message, and updating it.
|
||
|
|
||
|
*/
|
||
|
KS_DECLARE(int) ks_dht_generate_mutable_storage_args(struct bencode *data, int64_t sequence, int cas,
|
||
|
unsigned char *id, int id_len, /* querying nodes id */
|
||
|
const unsigned char *sk, const unsigned char *pk,
|
||
|
unsigned char *salt, unsigned long long salt_length,
|
||
|
unsigned char *token, unsigned long long token_length,
|
||
|
unsigned char *signature, unsigned long long *signature_length,
|
||
|
struct bencode **arguments)
|
||
|
{
|
||
|
struct bencode *arg = NULL, *sig = NULL;
|
||
|
unsigned char *encoded_message = NULL, *encoded_data = NULL;
|
||
|
size_t encoded_message_size = 0, encoded_data_size = 0;
|
||
|
int err = 0;
|
||
|
|
||
|
if ( !data || !sequence || !id || !id_len || !sk || !pk ||
|
||
|
!token || !token_length || !signature || !signature_length) {
|
||
|
ks_log(KS_LOG_ERROR, "Missing required input\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if ( arguments && *arguments) {
|
||
|
ks_log(KS_LOG_ERROR, "Arguments already defined.\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if ( salt && salt_length > 64 ) {
|
||
|
ks_log(KS_LOG_ERROR, "Salt is too long. Can not be longer than 64 bytes\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if ( sequence && sequence < 0 ) {
|
||
|
ks_log(KS_LOG_ERROR, "Sequence out of acceptable range\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
encoded_data = (unsigned char *) ben_encode(&encoded_data_size, data);
|
||
|
|
||
|
if ( encoded_data_size > 1000 ) {
|
||
|
ks_log(KS_LOG_ERROR, "Message is too long. Max is 1000 bytes\n");
|
||
|
free(encoded_data);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* Need to dynamically allocate a bencoded object for the signature. */
|
||
|
sig = ben_dict();
|
||
|
|
||
|
if ( salt ) {
|
||
|
ben_dict_set(sig, ben_blob("salt", 4), ben_blob(salt, salt_length));
|
||
|
}
|
||
|
|
||
|
ben_dict_set(sig, ben_blob("seq", 3), ben_int(sequence));
|
||
|
ben_dict_set(sig, ben_blob("v", 1), ben_blob(encoded_data, encoded_data_size));
|
||
|
|
||
|
encoded_message = ben_encode(&encoded_message_size, sig);
|
||
|
ks_log(KS_LOG_DEBUG, "Encoded data %d [%.*s]\n", encoded_message_size, encoded_message_size, encoded_message);
|
||
|
|
||
|
err = crypto_sign_detached(signature, NULL, encoded_message, encoded_message_size, sk);
|
||
|
if ( err ) {
|
||
|
ks_log(KS_LOG_ERROR, "Failed to sign message with provided secret key\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
free(encoded_message);
|
||
|
ben_free(sig);
|
||
|
|
||
|
arg = ben_dict();
|
||
|
|
||
|
if ( cas ) {
|
||
|
ben_dict_set(arg, ben_blob("cas", 3), ben_int(cas));
|
||
|
}
|
||
|
|
||
|
ben_dict_set(arg, ben_blob("id", 2), ben_blob(id, id_len));
|
||
|
ben_dict_set(arg, ben_blob("k", 1), ben_blob(pk, 32)); /* All ed25519 public keys are 32 bytes */
|
||
|
|
||
|
if ( salt ) {
|
||
|
ben_dict_set(arg, ben_blob("salt", 4), ben_blob(salt, salt_length));
|
||
|
}
|
||
|
|
||
|
ben_dict_set(arg, ben_blob("seq", 3), ben_int(sequence));
|
||
|
ben_dict_set(arg, ben_blob("sig", 3), ben_blob(signature, (size_t) *signature_length));
|
||
|
ben_dict_set(arg, ben_blob("token", 5), ben_blob(token, token_length));
|
||
|
ben_dict_set(arg, ben_blob("v", 1), ben_blob(encoded_data, encoded_data_size));
|
||
|
|
||
|
*arguments = arg;
|
||
|
|
||
|
free(encoded_data);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) ks_dht_calculate_mutable_storage_target(unsigned char *pk, unsigned char *salt, int salt_length, unsigned char *target, int target_length)
|
||
|
{
|
||
|
SHA_CTX sha;
|
||
|
unsigned char sha1[20] = {0};
|
||
|
|
||
|
/* Generate target sha-1 hash */
|
||
|
SHA1_Init(&sha);
|
||
|
SHA1_Update(&sha, pk, 32);
|
||
|
|
||
|
if ( salt ) {
|
||
|
SHA1_Update(&sha, salt, salt_length);
|
||
|
}
|
||
|
|
||
|
SHA1_Final(sha1, &sha);
|
||
|
b64encode(sha1, 20, target, target_length);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) ks_dht_send_message_mutable_cjson(dht_handle_t *h, unsigned char *sk, unsigned char *pk, char **node_id,
|
||
|
char *message_id, int sequence, cJSON *message, ks_time_t life)
|
||
|
{
|
||
|
struct bencode *body = ben_dict();
|
||
|
char *output = NULL;
|
||
|
char *json = cJSON_PrintUnformatted(message);
|
||
|
int err = 0;
|
||
|
size_t output_len = 0;
|
||
|
|
||
|
ben_dict_set(body, ben_blob("ct", 2), ben_blob("json", 4));
|
||
|
ben_dict_set(body, ben_blob("b", 1), ben_blob(json, strlen(json)));
|
||
|
|
||
|
output = (char *)ben_encode(&output_len, body);
|
||
|
|
||
|
err = ks_dht_send_message_mutable(h, sk, pk, node_id, message_id, sequence, output, life);
|
||
|
free(json);
|
||
|
free(output);
|
||
|
ben_free(body);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
KS_DECLARE(int) ks_dht_send_message_mutable(dht_handle_t *h, unsigned char *sk, unsigned char *pk, char **node_id,
|
||
|
char *message_id, int sequence, char *message, ks_time_t life)
|
||
|
{
|
||
|
unsigned char target[40], signature[crypto_sign_BYTES];
|
||
|
unsigned long long signature_length = crypto_sign_BYTES;
|
||
|
int message_length = strlen(message);
|
||
|
unsigned char tid[4];
|
||
|
unsigned char *salt = (unsigned char *)message_id;
|
||
|
int salt_length = strlen(message_id);
|
||
|
struct ks_dht_store_entry_s *entry = NULL;
|
||
|
struct bencode *b_message = ben_blob(message, message_length);
|
||
|
struct bencode *args = NULL, *data = NULL;
|
||
|
char buf[1500];
|
||
|
size_t buf_len = 0;
|
||
|
int err = 0;
|
||
|
h->now = ks_time_now_sec();
|
||
|
|
||
|
if ( !life ) {
|
||
|
/* Default to now plus 10 minutes */
|
||
|
life = 600;
|
||
|
}
|
||
|
|
||
|
make_tid(tid, "mm", 0);
|
||
|
|
||
|
ks_dht_calculate_mutable_storage_target(pk, salt, salt_length, target, 40);
|
||
|
|
||
|
if ( (entry = ks_dht_store_fetch(h->store, (char *)target)) ) {
|
||
|
if ( sequence < entry->serial ) {
|
||
|
sequence = entry->serial;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
int ks_dht_generate_mutable_storage_args(struct bencode *data, int64_t sequence, int cas,
|
||
|
unsigned char *id, int id_len,
|
||
|
const unsigned char *sk, const unsigned char *pk,
|
||
|
unsigned char *salt, unsigned long long salt_length,
|
||
|
unsigned char *token, unsigned long long token_length,
|
||
|
unsigned char *signature, unsigned long long *signature_length,
|
||
|
struct bencode **arguments) */
|
||
|
|
||
|
|
||
|
err = ks_dht_generate_mutable_storage_args(b_message, sequence, 1,
|
||
|
h->myid, 20,
|
||
|
sk, pk,
|
||
|
salt, salt_length,
|
||
|
(unsigned char *) target, 40,
|
||
|
signature, &signature_length,
|
||
|
&args);
|
||
|
|
||
|
if ( err ) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
data = ben_dict();
|
||
|
ben_dict_set(data, ben_blob("a", 1), args);
|
||
|
ben_dict_set(data, ben_blob("t", 1), ben_blob(tid, 4));
|
||
|
ben_dict_set(data, ben_blob("y", 1), ben_blob("q", 1));
|
||
|
ben_dict_set(data, ben_blob("q", 1), ben_blob("put", 3));
|
||
|
|
||
|
buf_len = ben_encode2(buf, 1500, data);
|
||
|
|
||
|
err = ks_dht_store_entry_create(h, data, &entry, life, 1);
|
||
|
if ( err ) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
ks_dht_store_replace(h->store, entry);
|
||
|
|
||
|
/* dht_search() announce of this hash */
|
||
|
dht_search(h, (const unsigned char *)entry->key, h->port, AF_INET, NULL, NULL);
|
||
|
|
||
|
if ( node_id && node_id[0] ) {
|
||
|
/* We're being told where to send these messages. */
|
||
|
int x = 0;
|
||
|
|
||
|
for ( x = 0; node_id[x] != NULL; x++ ) {
|
||
|
unsigned char node_id_bin[20] = {0};
|
||
|
struct node *n = NULL;
|
||
|
size_t size = 0;
|
||
|
|
||
|
sodium_hex2bin(node_id_bin, 20, node_id[x], 40, ":", &size, NULL);
|
||
|
|
||
|
n = find_node(h, node_id_bin, AF_INET);
|
||
|
|
||
|
if ( !n ) {
|
||
|
n = find_node(h, node_id_bin, AF_INET6);
|
||
|
}
|
||
|
|
||
|
if ( !n ) {
|
||
|
ks_log(KS_LOG_INFO, "Unable to find node with id\n");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
err |= dht_send(h, buf, buf_len, 0, &n->ss);
|
||
|
}
|
||
|
} else {
|
||
|
/* Client api assumes that we'll figure out where to send the message.
|
||
|
We should find a bucket that resolves to the key, and send to all nodes in that bucket.
|
||
|
*/
|
||
|
struct bucket *b4 = find_bucket(h, (const unsigned char *)entry->key, AF_INET);
|
||
|
struct bucket *b6 = find_bucket(h, (const unsigned char *)entry->key, AF_INET6);
|
||
|
struct node *n = NULL;
|
||
|
|
||
|
if ( b4 ) {
|
||
|
for ( n = b4->nodes; n->next; n = n->next ) {
|
||
|
err |= dht_send(h, buf, buf_len, 0, &n->ss);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( b6 ) {
|
||
|
for ( n = b6->nodes; n->next; n = n->next ) {
|
||
|
err |= dht_send(h, buf, buf_len, 0, &n->ss);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
// KS_DECLARE(int) ks_dht_send_message_mutable(
|
||
|
KS_DECLARE(int) ks_dht_api_find_node(dht_handle_t *h, char *node_id_hex, char *target_hex, ks_bool_t ipv6)
|
||
|
{
|
||
|
unsigned char node_id[20] = {0}, target[20] = {0}, tid[4] = {0};
|
||
|
struct node *n = NULL;
|
||
|
size_t size = 0;
|
||
|
|
||
|
if ( strlen(node_id_hex) != 40 || strlen(target_hex) != 40 ) {
|
||
|
ks_log(KS_LOG_INFO, "node_id(%s)[%d] and target(%s)[%d] must each be 40 hex characters\n", node_id_hex, strlen(node_id_hex), target_hex, strlen(target_hex));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sodium_hex2bin(node_id, 20, node_id_hex, 40, ":", &size, NULL);
|
||
|
sodium_hex2bin(target, 20, target_hex, 40, ":", &size, NULL);
|
||
|
|
||
|
n = find_node(h, node_id, ipv6 ? AF_INET6 : AF_INET);
|
||
|
|
||
|
if ( !n ) {
|
||
|
ks_log(KS_LOG_INFO, "Unable to find node with id[%s]\n", node_id_hex);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
make_tid(tid, "fn", 0);
|
||
|
|
||
|
return send_find_node(h, &n->ss, tid, 4, target, sizeof(target), WANT4 | WANT6, n->reply_time >= h->now - 15);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* For Emacs:
|
||
|
* Local Variables:
|
||
|
* mode:c
|
||
|
* indent-tabs-mode:t
|
||
|
* tab-width:4
|
||
|
* c-basic-offset:4
|
||
|
* End:
|
||
|
* For VIM:
|
||
|
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
|
||
|
*/
|