/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "dht.h" #define MAX_BOOTSTRAP_NODES 20 static struct sockaddr_storage bootstrap_nodes[MAX_BOOTSTRAP_NODES]; static int num_bootstrap_nodes = 0; static volatile sig_atomic_t dumping = 0, searching = 0, exiting = 0; static void sigdump(int signo) { dumping = 1; } static void sigtest(int signo) { searching = 1; } static void sigexit(int signo) { exiting = 1; } static void init_signals(void) { struct sigaction sa; sigset_t ss; sigemptyset(&ss); sa.sa_handler = sigdump; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGUSR1, &sa, NULL); sigemptyset(&ss); sa.sa_handler = sigtest; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGUSR2, &sa, NULL); sigemptyset(&ss); sa.sa_handler = sigexit; sa.sa_mask = ss; sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); } const unsigned char hash[20] = { 0x54, 0x57, 0x87, 0x89, 0xdf, 0xc4, 0x23, 0xee, 0xf6, 0x03, 0x1f, 0x81, 0x94, 0xa9, 0x3a, 0x16, 0x98, 0x8b, 0x72, 0x7b }; /* 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, int event, const unsigned char *info_hash, const void *data, size_t data_len) { if(event == DHT_EVENT_SEARCH_DONE) printf("Search done.\n"); else if(event == DHT_EVENT_SEARCH_DONE) printf("IPv6 search done.\n"); else if(event == DHT_EVENT_VALUES) printf("Received %d values.\n", (int)(data_len / 6)); else if(event == DHT_EVENT_VALUES6) printf("Received %d IPv6 values.\n", (int)(data_len / 18)); else printf("Unknown DHT event %d.\n", event); } static unsigned char buf[4096]; static int set_nonblocking(int fd, int nonblocking) { int rc; rc = fcntl(fd, F_GETFL, 0); if(rc < 0) return -1; rc = fcntl(fd, F_SETFL, nonblocking ? (rc | O_NONBLOCK) : (rc & ~O_NONBLOCK)); if(rc < 0) return -1; return 0; } int main(int argc, char **argv) { int i, rc, fd; int s = -1, s6 = -1, port; int have_id = 0; unsigned char myid[20]; time_t tosleep = 0; char *id_file = "dht-example.id"; int opt; int quiet = 0, ipv4 = 1, ipv6 = 1; struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr_storage from; socklen_t fromlen; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; while(1) { opt = getopt(argc, argv, "q46b:i:"); if(opt < 0) break; switch(opt) { case 'q': quiet = 1; break; case '4': ipv6 = 0; break; case '6': ipv4 = 0; break; case 'b': { char buf[16]; int rc; rc = inet_pton(AF_INET, optarg, buf); if(rc == 1) { memcpy(&sin.sin_addr, buf, 4); break; } rc = inet_pton(AF_INET6, optarg, buf); if(rc == 1) { memcpy(&sin6.sin6_addr, buf, 16); break; } goto usage; } break; case 'i': id_file = optarg; break; default: goto usage; } } /* Ids need to be distributed evenly, so you cannot just use your bittorrent id. Either generate it randomly, or take the SHA-1 of something. */ fd = open(id_file, O_RDONLY); if(fd >= 0) { rc = read(fd, myid, 20); if(rc == 20) have_id = 1; close(fd); } fd = open("/dev/urandom", O_RDONLY); if(fd < 0) { perror("open(random)"); exit(1); } if(!have_id) { int ofd; rc = read(fd, myid, 20); if(rc < 0) { perror("read(random)"); exit(1); } have_id = 1; close(fd); ofd = open(id_file, O_WRONLY | O_CREAT | O_TRUNC, 0666); if(ofd >= 0) { rc = write(ofd, myid, 20); if(rc < 20) unlink(id_file); close(ofd); } } { unsigned seed; read(fd, &seed, sizeof(seed)); srandom(seed); } close(fd); if(argc < 2) goto usage; i = optind; if(argc < i + 1) goto usage; port = atoi(argv[i++]); if(port <= 0 || port >= 0x10000) goto usage; while(i < argc) { struct addrinfo hints, *info, *infop; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; if(!ipv6) hints.ai_family = AF_INET; else if(!ipv4) hints.ai_family = AF_INET6; else hints.ai_family = 0; rc = getaddrinfo(argv[i], argv[i + 1], &hints, &info); if(rc != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc)); exit(1); } i++; if(i >= argc) goto usage; infop = info; while(infop) { memcpy(&bootstrap_nodes[num_bootstrap_nodes], infop->ai_addr, infop->ai_addrlen); infop = infop->ai_next; num_bootstrap_nodes++; } freeaddrinfo(info); i++; } /* If you set dht_debug to a stream, every action taken by the DHT will be logged. */ if(!quiet) dht_debug = stdout; /* We need an IPv4 and an IPv6 socket, bound to a stable port. Rumour has it that uTorrent likes you better when it is the same as your Bittorrent port. */ if(ipv4) { s = socket(PF_INET, SOCK_DGRAM, 0); if(s < 0) { perror("socket(IPv4)"); } rc = set_nonblocking(s, 1); if(rc < 0) { perror("set_nonblocking(IPv4)"); exit(1); } } if(ipv6) { s6 = socket(PF_INET6, SOCK_DGRAM, 0); if(s6 < 0) { perror("socket(IPv6)"); } rc = set_nonblocking(s6, 1); if(rc < 0) { perror("set_nonblocking(IPv6)"); exit(1); } } if(s < 0 && s6 < 0) { fprintf(stderr, "Eek!"); exit(1); } if(s >= 0) { sin.sin_port = htons(port); rc = bind(s, (struct sockaddr*)&sin, sizeof(sin)); if(rc < 0) { perror("bind(IPv4)"); exit(1); } } if(s6 >= 0) { int rc; int val = 1; rc = setsockopt(s6, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&val, sizeof(val)); if(rc < 0) { perror("setsockopt(IPV6_V6ONLY)"); exit(1); } /* BEP-32 mandates that we should bind this socket to one of our global IPv6 addresses. In this simple example, this only happens if the user used the -b flag. */ sin6.sin6_port = htons(port); rc = bind(s6, (struct sockaddr*)&sin6, sizeof(sin6)); if(rc < 0) { perror("bind(IPv6)"); exit(1); } } /* Init the dht. */ rc = dht_init(s, s6, myid, (unsigned char*)"JC\0\0"); if(rc < 0) { perror("dht_init"); exit(1); } init_signals(); /* 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 know the nodes' ids, it is better to use dht_insert_node. */ for(i = 0; i < num_bootstrap_nodes; i++) { socklen_t salen; if(bootstrap_nodes[i].ss_family == AF_INET) salen = sizeof(struct sockaddr_in); else salen = sizeof(struct sockaddr_in6); dht_ping_node((struct sockaddr*)&bootstrap_nodes[i], salen); /* Don't overload the DHT, or it will drop your nodes. */ if(i <= 128) usleep(random() % 10000); else usleep(500000 + random() % 400000); } while(1) { struct timeval tv; fd_set readfds; tv.tv_sec = tosleep; tv.tv_usec = random() % 1000000; FD_ZERO(&readfds); if(s >= 0) FD_SET(s, &readfds); if(s6 >= 0) FD_SET(s6, &readfds); rc = select(s > s6 ? s + 1 : s6 + 1, &readfds, NULL, NULL, &tv); if(rc < 0) { if(errno != EINTR) { perror("select"); sleep(1); } } if(exiting) break; if(rc > 0) { fromlen = sizeof(from); if(s >= 0 && FD_ISSET(s, &readfds)) rc = recvfrom(s, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&from, &fromlen); else if(s6 >= 0 && FD_ISSET(s6, &readfds)) rc = recvfrom(s6, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&from, &fromlen); else abort(); } if(rc > 0) { buf[rc] = '\0'; rc = dht_periodic(buf, rc, (struct sockaddr*)&from, fromlen, &tosleep, callback, NULL); } else { rc = dht_periodic(NULL, 0, NULL, 0, &tosleep, callback, NULL); } if(rc < 0) { if(errno == EINTR) { continue; } else { perror("dht_periodic"); if(rc == EINVAL || rc == EFAULT) abort(); tosleep = 1; } } /* This is how you trigger a search for a torrent hash. If port (the second argument) is non-zero, it also performs an announce. Since peers expire announced data after 30 minutes, it is a good idea to reannounce every 28 minutes or so. */ if(searching) { if(s >= 0) dht_search(hash, 0, AF_INET, callback, NULL); if(s6 >= 0) dht_search(hash, 0, AF_INET6, callback, NULL); searching = 0; } /* For debugging, or idle curiosity. */ if(dumping) { dht_dump_tables(stdout); dumping = 0; } } { struct sockaddr_in sin[500]; struct sockaddr_in6 sin6[500]; int num = 500, num6 = 500; int i; i = dht_get_nodes(sin, &num, sin6, &num6); printf("Found %d (%d + %d) good nodes.\n", i, num, num6); } dht_uninit(); return 0; usage: printf("Usage: dht-example [-q] [-4] [-6] [-i filename] [-b address]...\n" " port [address port]...\n"); exit(1); } /* Functions called by the DHT. */ int dht_sendto(int sockfd, const void *buf, int len, int flags, const struct sockaddr *to, int tolen) { return sendto(sockfd, buf, len, flags, to, tolen); } int dht_blacklisted(const struct sockaddr *sa, int salen) { return 0; } /* We need to provide a reasonably strong cryptographic hashing function. Here's how we'd do it if we had RSA's MD5 code. */ #if 0 void dht_hash(void *hash_return, int hash_size, const void *v1, int len1, const void *v2, int len2, const void *v3, int len3) { static MD5_CTX ctx; MD5Init(&ctx); MD5Update(&ctx, v1, len1); MD5Update(&ctx, v2, len2); MD5Update(&ctx, v3, len3); MD5Final(&ctx); if(hash_size > 16) memset((char*)hash_return + 16, 0, hash_size - 16); memcpy(hash_return, ctx.digest, hash_size > 16 ? 16 : hash_size); } #else /* But for this toy example, we might as well use something weaker. */ void dht_hash(void *hash_return, int hash_size, const void *v1, int len1, const void *v2, int len2, const void *v3, int len3) { const char *c1 = v1, *c2 = v2, *c3 = v3; char key[9]; /* crypt is limited to 8 characters */ int i; memset(key, 0, 9); #define CRYPT_HAPPY(c) ((c % 0x60) + 0x20) for(i = 0; i < 2 && i < len1; i++) key[i] = CRYPT_HAPPY(c1[i]); for(i = 0; i < 4 && i < len1; i++) key[2 + i] = CRYPT_HAPPY(c2[i]); for(i = 0; i < 2 && i < len1; i++) key[6 + i] = CRYPT_HAPPY(c3[i]); strncpy(hash_return, crypt(key, "jc"), hash_size); } #endif int dht_random_bytes(void *buf, size_t size) { int fd, rc, save; fd = open("/dev/urandom", O_RDONLY); if(fd < 0) return -1; rc = read(fd, buf, size); save = errno; close(fd); errno = save; return rc; }