410 lines
11 KiB
C
410 lines
11 KiB
C
/* This example code was written by Juliusz Chroboczek.
|
|
You are free to cut'n'paste from it to your heart's content. */
|
|
|
|
/* For crypt */
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/time.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <sys/signal.h>
|
|
|
|
#include "ks.h"
|
|
#include "histedit.h"
|
|
#include "sodium.h"
|
|
|
|
#define MAX_BOOTSTRAP_NODES 20
|
|
static ks_sockaddr_t bootstrap_nodes[MAX_BOOTSTRAP_NODES];
|
|
static ks_sockaddr_t bind_nodes[MAX_BOOTSTRAP_NODES];
|
|
static int num_bootstrap_nodes = 0;
|
|
static int num_bind_nodes = 0;
|
|
|
|
/* The call-back function is called by the DHT whenever something
|
|
interesting happens. Right now, it only happens when we get a new value or
|
|
when a search completes, but this may be extended in future versions. */
|
|
static void callback(void *closure, ks_dht_event_t event, const unsigned char *info_hash, const void *data, size_t data_len)
|
|
{
|
|
if(event == KS_DHT_EVENT_SEARCH_DONE) {
|
|
printf("Search done.\n");
|
|
} else if(event == KS_DHT_EVENT_VALUES) {
|
|
const uint8_t *bits_8 = data;
|
|
const uint16_t *bits_16 = data;
|
|
|
|
printf("Received %d values.\n", (int)(data_len / 6));
|
|
printf("Recieved %u.%u.%u.%u:%u\n", bits_8[0], bits_8[1], bits_8[2], bits_8[3], ntohs(bits_16[2]));
|
|
} else {
|
|
printf("Unhandled event %d\n", event);
|
|
}
|
|
}
|
|
|
|
void json_cb(struct dht_handle_s *h, const cJSON *msg, void *arg)
|
|
{
|
|
char *pretty = cJSON_Print((cJSON *)msg);
|
|
|
|
printf("Received json msg: %s\n", pretty);
|
|
|
|
free(pretty);
|
|
}
|
|
|
|
static char * prompt(EditLine *e) {
|
|
return "dht> ";
|
|
}
|
|
|
|
static dht_handle_t *h;
|
|
|
|
|
|
typedef struct dht_globals_s {
|
|
int s;
|
|
int s6;
|
|
int port;
|
|
int exiting;
|
|
} dht_globals_t;
|
|
|
|
void *dht_event_thread(ks_thread_t *thread, void *data)
|
|
{
|
|
dht_globals_t *globals = data;
|
|
|
|
while(!globals->exiting) {
|
|
ks_dht_one_loop(h, 0);
|
|
ks_sleep(1000000);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
dht_globals_t globals = {0};
|
|
int i;
|
|
//int have_id = 0;
|
|
//char *id_file = "dht-example.id";
|
|
int ipv4 = 0, ipv6 = 0;
|
|
int autobind = 0;
|
|
int opt;
|
|
EditLine *el;
|
|
History *myhistory;
|
|
int count;
|
|
const char *line;
|
|
HistEvent ev;
|
|
ks_status_t status;
|
|
static ks_thread_t *threads[1]; /* Main dht event thread */
|
|
ks_pool_t *pool;
|
|
int err = 0;
|
|
unsigned char alice_publickey[crypto_sign_PUBLICKEYBYTES] = {0};
|
|
unsigned char alice_secretkey[crypto_sign_SECRETKEYBYTES] = {0};
|
|
|
|
ks_init();
|
|
|
|
el = el_init("test", stdin, stdout, stderr);
|
|
el_set(el, EL_PROMPT, &prompt);
|
|
el_set(el, EL_EDITOR, "emacs");
|
|
myhistory = history_init();
|
|
history(myhistory, &ev, H_SETSIZE, 800);
|
|
el_set(el, EL_HIST, history, myhistory);
|
|
globals.port = 5309;
|
|
|
|
|
|
ks_global_set_default_logger(7);
|
|
|
|
while(1) {
|
|
opt = getopt(argc, argv, "46ap:b:B:");
|
|
if(opt < 0)
|
|
break;
|
|
|
|
switch(opt) {
|
|
case '4':
|
|
ipv4 = 1;
|
|
break;
|
|
case '6':
|
|
ipv6 = 1;
|
|
break;
|
|
case 'a':
|
|
autobind = 1;
|
|
break;
|
|
case 'p':
|
|
globals.port = atoi(optarg);
|
|
break;
|
|
case 'b':
|
|
case 'B': {
|
|
char ip[80];
|
|
int port = globals.port;
|
|
char *p;
|
|
ks_set_string(ip, optarg);
|
|
|
|
if ((p = strchr(ip, '+'))) {
|
|
*p++ = '\0';
|
|
port = atoi(p);
|
|
}
|
|
if (opt == 'B') {
|
|
printf("Adding bootstrap node %s:%d\n", ip, port);
|
|
ks_addr_set(&bootstrap_nodes[num_bootstrap_nodes++], ip, port, 0);
|
|
} else {
|
|
printf("Adding binding %s:%d\n", ip, port);
|
|
ks_addr_set(&bind_nodes[num_bind_nodes++], ip, port, 0);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
goto usage;
|
|
}
|
|
}
|
|
|
|
if(argc < 2)
|
|
goto usage;
|
|
|
|
i = optind;
|
|
|
|
if(globals.port <= 0 || globals.port >= 0x10000)
|
|
goto usage;
|
|
|
|
|
|
ks_dht_af_flag_t af_flags = 0;
|
|
|
|
if (ipv4) {
|
|
af_flags |= KS_DHT_AF_INET4;
|
|
}
|
|
|
|
if (ipv6) {
|
|
af_flags |= KS_DHT_AF_INET6;
|
|
}
|
|
|
|
/* Init the dht. */
|
|
status = ks_dht_init(&h, af_flags, NULL, globals.port);
|
|
|
|
if(status != KS_STATUS_SUCCESS) {
|
|
perror("dht_init");
|
|
exit(1);
|
|
}
|
|
|
|
for(i = 0; i < num_bind_nodes; i++) {
|
|
ks_dht_add_ip(h, bind_nodes[i].host, bind_nodes[i].port);
|
|
}
|
|
|
|
if (autobind) {
|
|
ks_dht_set_param(h, DHT_PARAM_AUTOROUTE, KS_TRUE);
|
|
}
|
|
|
|
ks_dht_start(h);
|
|
|
|
ks_dht_set_callback(h, callback, NULL);
|
|
|
|
ks_pool_open(&pool);
|
|
status = ks_thread_create_ex(&threads[0], dht_event_thread, &globals, KS_THREAD_FLAG_DETATCHED, KS_THREAD_DEFAULT_STACK, KS_PRI_NORMAL, pool);
|
|
|
|
if ( status != KS_STATUS_SUCCESS) {
|
|
printf("Failed to start DHT event thread\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* For bootstrapping, we need an initial list of nodes. This could be
|
|
hard-wired, but can also be obtained from the nodes key of a torrent
|
|
file, or from the PORT bittorrent message.
|
|
|
|
Dht_ping_node is the brutal way of bootstrapping -- it actually
|
|
sends a message to the peer. If you're going to bootstrap from
|
|
a massive number of nodes (for example because you're restoring from
|
|
a dump) and you already know their ids, it's better to use
|
|
dht_insert_node. If the ids are incorrect, the DHT will recover. */
|
|
for(i = 0; i < num_bootstrap_nodes; i++) {
|
|
dht_ping_node(h, &bootstrap_nodes[i]);
|
|
usleep(random() % 100000);
|
|
}
|
|
|
|
printf("TESTING!!!\n");
|
|
err = crypto_sign_keypair(alice_publickey, alice_secretkey);
|
|
printf("Result of generating keypair %d\n", err);
|
|
|
|
ks_dht_store_entry_json_cb_set(h, json_cb, NULL);
|
|
|
|
while ( !globals.exiting ) {
|
|
line = el_gets(el, &count);
|
|
|
|
if (count > 1) {
|
|
int line_len = (int)strlen(line) - 1;
|
|
char *cmd_dup = strdup(line);
|
|
char *argv[8] = { 0 };
|
|
int argc = 0;
|
|
|
|
history(myhistory, &ev, H_ENTER, line);
|
|
|
|
if ( cmd_dup[line_len] == '\n' ) {
|
|
cmd_dup[line_len] = '\0';
|
|
}
|
|
argc = ks_separate_string(cmd_dup, " ", argv, (sizeof(argv) / sizeof(argv[0])));
|
|
|
|
if (!strncmp(line, "quit", 4)) {
|
|
globals.exiting = 1;
|
|
} else if (!strncmp(line, "show_bind", 9)) {
|
|
const ks_sockaddr_t **bindings;
|
|
ks_size_t len = 0;
|
|
int i;
|
|
|
|
ks_dht_get_bind_addrs(h, &bindings, &len);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
printf("Bind addr %s:%d\n", bindings[i]->host, bindings[i]->port);
|
|
}
|
|
|
|
} else if (!strncmp(line, "ping ", 5)) {
|
|
const char *ip = line + 5;
|
|
ks_sockaddr_t tmp;
|
|
char *p;
|
|
|
|
while ((p = strchr(ip, '\r')) || (p = strchr(ip, '\n'))) {
|
|
*p = '\0';
|
|
}
|
|
|
|
ks_addr_set(&tmp, ip, globals.port, 0);
|
|
dht_ping_node(h, &tmp);
|
|
} else if (!strncmp(line, "find_node ", 9)) {
|
|
/* usage: find_node ipv[4|6] [40 character node id] [40 character target id] */
|
|
ks_bool_t ipv6 = strncmp(argv[1], "ipv4", 4);
|
|
(void) argc; /* Check to see if it's the right length, else print usage */
|
|
ks_dht_api_find_node(h, argv[2], argv[3], ipv6);
|
|
} else if (!strncmp(line, "loglevel", 8)) {
|
|
ks_global_set_default_logger(atoi(line + 9));
|
|
} else if (!strncmp(line, "peer_dump", 9)) {
|
|
dht_dump_tables(h, stdout);
|
|
} else if (!strncmp(line, "generate_identity", 17)) {
|
|
/* usage: generate_identity [identity key: first_id] */
|
|
/* requires an arg, checks identity hash for arg value.
|
|
|
|
if found, return already exists.
|
|
if not found, generate sodium public and private keys, and insert into identities hash.
|
|
*/
|
|
} else if (!strncmp(line, "print_identity_key", 18)) {
|
|
/* usage: print_identity_key [identity key] */
|
|
} else if (!strncmp(line, "message_mutable", 15)) {
|
|
char *input = strdup(line);
|
|
char *identity_key = input + 16;
|
|
char *identities[2] = { identity_key, NULL };
|
|
char *message_id = NULL;
|
|
char *message = NULL;
|
|
cJSON *output = NULL;
|
|
int idx = 17; /* this should be the start of the message_id */
|
|
for ( idx = 17; idx < 100 && input[idx] != '\0'; idx++ ) {
|
|
if ( input[idx] == ' ' ) {
|
|
input[idx] = '\0';
|
|
message_id = input + 1 + idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ( idx++; idx < 100 && input[idx] != '\0'; idx++ ) {
|
|
if ( input[idx] == ' ' ) {
|
|
input[idx] = '\0';
|
|
message = input + 1 + idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Hack for my testing, so that it chomps the new line. Makes debugging print nicer. */
|
|
for ( idx++; input[idx] != '\0'; idx++) {
|
|
if ( input[idx] == '\n' ) {
|
|
input[idx] = '\0';
|
|
}
|
|
}
|
|
/* usage: message_mutable [identity key] [message id: asdf] [your message: Hello from DHT example]*/
|
|
/*
|
|
takes an identity, a message id(salt) and a message, then sends out the announcement.
|
|
*/
|
|
output = cJSON_CreateString(message);
|
|
|
|
ks_dht_send_message_mutable_cjson(h, alice_secretkey, alice_publickey,
|
|
identities, message_id, 1, output, 600);
|
|
free(input);
|
|
cJSON_Delete(output);
|
|
} else if (!strncmp(line, "message_immutable", 15)) {
|
|
/* usage: message_immutable [identity key] */
|
|
/*
|
|
takes an identity, and a message, then sends out the announcement.
|
|
*/
|
|
} else if (!strncmp(line, "message_get", 11)) {
|
|
/* usage: message_get [40 character sha1 digest b64 encoded]*/
|
|
|
|
/* MUST RETURN BENCODE OBJECT */
|
|
} else if (!strncmp(line, "message_get_mine", 16)) {
|
|
/* usage: message_get [identity key] [message id: asdf]*/
|
|
/* This looks up the message token from identity key and the message id(aka message salt) */
|
|
|
|
/* MUST RETURN BENCODE OBJECT */
|
|
} else if (!strncmp(line, "add_buddy", 9)) {
|
|
/* usage: add_buddy [buddy key] [buddy public key] */
|
|
|
|
} else if (!strncmp(line, "get_buddy_message", 17)) {
|
|
/* usage: get_buddy_message [buddy key] [buddy message_id] */
|
|
|
|
|
|
} else if (!strncmp(line, "search", 6)) {
|
|
if ( line_len > 7 ) {
|
|
unsigned char hash[20];
|
|
memcpy(hash, line + 7, 20);
|
|
|
|
if(globals.s >= 0) {
|
|
dht_search(h, hash, 0, AF_INET, callback, NULL);
|
|
}
|
|
} else {
|
|
printf("Your search string isn't a valid 20 character hash. You entered [%.*s] of length %d\n", line_len - 7, line + 7, line_len - 7);
|
|
}
|
|
} else if (!strncmp(line, "announce", 8)) {
|
|
if ( line_len == 29 ) {
|
|
unsigned char hash[20];
|
|
memcpy(hash, line + 9, 20);
|
|
|
|
if(globals.s >= 0) {
|
|
dht_search(h, hash, globals.port, AF_INET, callback, NULL);
|
|
}
|
|
} else {
|
|
printf("Your search string isn't a valid 20 character hash. You entered [%.*s]\n", line_len - 7, line + 7);
|
|
}
|
|
} else {
|
|
printf("Unknown command entered[%.*s]\n", line_len, line);
|
|
}
|
|
|
|
free(cmd_dup);
|
|
}
|
|
}
|
|
|
|
{
|
|
struct sockaddr_in sin[500];
|
|
struct sockaddr_in6 sin6[500];
|
|
int num = 500, num6 = 500;
|
|
int i;
|
|
i = dht_get_nodes(h, sin, &num, sin6, &num6);
|
|
printf("Found %d (%d + %d) good nodes.\n", i, num, num6);
|
|
}
|
|
|
|
|
|
history_end(myhistory);
|
|
el_end(el);
|
|
dht_uninit(&h);
|
|
ks_shutdown();
|
|
return 0;
|
|
|
|
usage:
|
|
printf("Usage: dht-example [-a] [-4] [-6] [-p <port>] [-b <ip>[+<port>]]...\n"
|
|
" [-B <ip>[+<port>]]...\n");
|
|
exit(0);
|
|
}
|
|
|
|
|
|
/* 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:
|
|
*/
|