/* Copyright (c) 2009-2011 by Juliusz Chroboczek 2022 by Sergey Matveev 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef MIN #define MIN(x, y) ((x) <= (y) ? (x) : (y)) #define MAX_PKT_SIZE 1536 #define ERROR 0 #define REPLY 1 #define PING 2 #define FIND_NODE 3 #define GET_PEERS 4 #define ANNOUNCE_PEER 5 #define WANT4 1 #define WANT6 2 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}; static unsigned char myid[20]; static unsigned char my_v[9]; static int dht_socket = -1; static int dht_socket6 = -1; struct node { unsigned char id[160]; struct sockaddr_storage ss; socklen_t sslen; char _pad[4]; }; #define CIRCULAR_LIST_SIZE 256 struct circular_list { int head; int tail; struct node nodes[CIRCULAR_LIST_SIZE]; }; static struct circular_list v4_new, v6_new, v4_confirmed, v6_confirmed; #define MAX_TOKEN_BUCKET_TOKENS 40 static time_t token_bucket_time; static int token_bucket_tokens; static bool verbose = true; struct Pkt { unsigned char buf[MAX_PKT_SIZE]; struct sockaddr_storage ss; size_t len; socklen_t salen; char _pad[4]; }; static struct Pkt pkt; static int sockW = -1; // ------------------------ >8 ------------------------ static const char * getAddr(const struct sockaddr *sa, const socklen_t saLen) { static char host[NI_MAXHOST]; static char port[NI_MAXSERV]; static char addr[64]; int rc = getnameinfo( sa, saLen, host, sizeof host, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV); if (rc != 0) { fprintf(stderr, "getnameinfo: %s\n", gai_strerror(rc)); return NULL; } snprintf(addr, sizeof addr, "[%s]:%s", host, port); return addr; } static bool stricttol(const char *p, unsigned long *l, char **q) { if ((isdigit((int)(p[0])) == 0) || (p[0] == '0')) return false; errno = 0; *l = strtoul(p, q, 10); if (errno != 0) { perror("strtoul()"); return false; } return true; } static int parse_message( const unsigned char *buf, const size_t buflen, unsigned char *tid_return, size_t *tid_len, unsigned char *id_return, unsigned char *info_hash_return, unsigned char *target_return, unsigned short *port_return, unsigned char *token_return, size_t *token_len, unsigned char *nodes_return, size_t *nodes_len, unsigned char *nodes6_return, size_t *nodes6_len, unsigned char *values_return, size_t *values_len, unsigned char *values6_return, size_t *values6_len, int *want_return, const struct sockaddr *sa, const socklen_t saLen) { /* This code will happily crash if the buffer is not NUL-terminated. */ if (buf[buflen] != '\0') { if (verbose) printf("%s : parse_message with unterminated buffer\n", getAddr(sa, saLen)); return -1; } #define CHECK(ptr, len) \ if (((const unsigned char *)ptr) + (len) > (buf) + (buflen)) \ goto overflow; unsigned char *p; unsigned long l = 0; char *q; if (tid_return) { p = memmem(buf, buflen, "1:t", 3); if (p) { if (stricttol((const char *)p + 3, &l, &q) && (q && *q == ':') && (l > 0 && l < *tid_len)) { CHECK(q + 1, l); memcpy(tid_return, q + 1, l); *tid_len = l; } else *tid_len = 0; } } if (id_return) { p = memmem(buf, buflen, "2:id20:", 7); if (p) { CHECK(p + 7, 20); memcpy(id_return, p + 7, 20); } else { memset(id_return, 0, 20); } } if (info_hash_return) { p = memmem(buf, buflen, "9:info_hash20:", 14); if (p) { CHECK(p + 14, 20); memcpy(info_hash_return, p + 14, 20); } else { memset(info_hash_return, 0, 20); } } if (port_return) { p = memmem(buf, buflen, "porti", 5); if (p) { if (stricttol((const char *)p + 5, &l, &q) && (q && *q == 'e') && (l > 0 && l < 0x10000)) *port_return = (unsigned short)l; else *port_return = 0; } else *port_return = 0; } if (target_return) { p = memmem(buf, buflen, "6:target20:", 11); if (p) { CHECK(p + 11, 20); memcpy(target_return, p + 11, 20); } else { memset(target_return, 0, 20); } } if (token_return) { p = memmem(buf, buflen, "5:token", 7); if (p) { if (stricttol((const char *)p + 7, &l, &q) && (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 = memmem(buf, buflen, "5:nodes", 7); if (p) { if (stricttol((const char *)p + 7, &l, &q) && (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 = memmem(buf, buflen, "6:nodes6", 8); if (p) { if (stricttol((const char *)p + 8, &l, &q) && (q && *q == ':') && (l > 0 && l < *nodes6_len)) { CHECK(q + 1, l); memcpy(nodes6_return, q + 1, l); *nodes6_len = l; } else *nodes6_len = 0; } else *nodes6_len = 0; } if (values_len || values6_len) { p = memmem(buf, buflen, "6:valuesl", 9); if (p) { size_t i = p - buf + 9; unsigned long j = 0, j6 = 0; for (;;) { if (stricttol((const char *)buf + i, &l, &q) && (q && *q == ':') && (l > 0)) { CHECK(q + 1, l); if (l == 6) { if (j + l > *values_len) continue; i = q + 1 + l - (const char *)buf; memcpy((char *)values_return + j, q + 1, l); j += l; } else if (l == 18) { if (j6 + l > *values6_len) continue; i = q + 1 + l - (const char *)buf; memcpy((char *)values6_return + j6, q + 1, l); j6 += l; } else { if (verbose) printf( "%s : received weird value: %d bytes\n", getAddr(sa, saLen), (int)l); i = q + 1 + l - (const char *)buf; } } else { break; } } if (i >= buflen || buf[i] != 'e') if (verbose) printf("%s : unexpected end for values\n", getAddr(sa, saLen)); if (values_len) *values_len = j; if (values6_len) *values6_len = j6; } else { *values_len = 0; *values6_len = 0; } } if (want_return) { p = memmem(buf, buflen, "4:wantl", 7); if (p) { size_t 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 if (verbose) printf( "%s : unexpected want flag: %c\n", getAddr(sa, saLen), buf[i]); i += 2 + buf[i] - '0'; } if (i >= buflen || buf[i] != 'e') if (verbose) printf("%s : unexpected end for want\n", getAddr(sa, saLen)); } else { *want_return = -1; } } #undef CHECK if (memmem(buf, buflen, "1:y1:r", 6)) return REPLY; if (memmem(buf, buflen, "1:y1:e", 6)) return ERROR; if (!memmem(buf, buflen, "1:y1:q", 6)) return -1; if (memmem(buf, buflen, "1:q4:ping", 9)) return PING; if (memmem(buf, buflen, "1:q9:find_node", 14)) return FIND_NODE; if (memmem(buf, buflen, "1:q9:get_peers", 14)) return GET_PEERS; if (memmem(buf, buflen, "1:q13:announce_peer", 19)) return ANNOUNCE_PEER; return -1; overflow: if (verbose) printf(": truncated message\n"); return -1; } #undef CHECK #undef INC #undef COPY #undef ADD_V /* 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) COPY(buf, offset, my_v, (sizeof my_v), size) static bool is_martian(const struct sockaddr_storage *ss) { switch (((const struct sockaddr *)ss)->sa_family) { case AF_INET: { const struct sockaddr_in *sin = (const struct sockaddr_in *)ss; const unsigned char *address = (const unsigned char *)&sin->sin_addr; return sin->sin_port == 0 || (address[0] == 0) || (address[0] == 127) || ((address[0] & 0xE0) == 0xE0); } case AF_INET6: { const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)ss; const unsigned char *address = (const unsigned char *)&sin6->sin6_addr; return sin6->sin6_port == 0 || (address[0] == 0xFF) || (address[0] == 0xFE && (address[1] & 0xC0) == 0x80) || (memcmp(address, zeroes, 15) == 0 && (address[15] == 0 || address[15] == 1)) || (memcmp(address, v4prefix, 12) == 0); } } return false; } /* Forget about the ``XOR-metric''. An id is just a path from the root of the tree, so bits are numbered from the start. */ static inline int id_cmp(const unsigned char *restrict id1, const unsigned char *restrict id2) { /* Memcmp is guaranteed to perform an unsigned comparison. */ return memcmp(id1, id2, 20); } /* 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, const unsigned short seqno) { tid_return[0] = prefix[0] & 0xFF; tid_return[1] = prefix[1] & 0xFF; memcpy(tid_return + 2, &seqno, 2); } static int tid_match(const unsigned char *tid, const char *prefix, unsigned short *seqno_return) { if (tid[0] == (prefix[0] & 0xFF) && tid[1] == (prefix[1] & 0xFF)) { if (seqno_return) memcpy(seqno_return, tid + 2, 2); return 1; } else return 0; } static inline int circular(int from, int to) { int x = to - from; if (x < 0) return x + CIRCULAR_LIST_SIZE; return x; } static int list_elements(struct circular_list *list) { return circular(list->head, list->tail); } static bool list_empty(struct circular_list *list) { return list_elements(list) == 0; } static int list_free(struct circular_list *list) { return circular(list->tail + 1, list->head); } static int list_pop(struct circular_list *list, struct sockaddr_storage *ss, socklen_t *sslen) { if (list->head == list->tail) return 0; memcpy(ss, &list->nodes[list->head].ss, list->nodes[list->head].sslen); *sslen = list->nodes[list->head].sslen; list->head = (list->head + 1) % CIRCULAR_LIST_SIZE; return 1; } static int list_random( struct circular_list *list, unsigned char *id, struct sockaddr_storage *ss, socklen_t *sslen) { if (list->head == list->tail) return 0; int n = random() % (list->tail - list->head); n = (list->head + n) % CIRCULAR_LIST_SIZE; if (id) memcpy(id, &list->nodes[n].id, 20); memcpy(ss, &list->nodes[n].ss, list->nodes[n].sslen); *sslen = list->nodes[n].sslen; return 1; } /* 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 bool new_node( const unsigned char *id, const struct sockaddr *sa, const socklen_t salen, const int confirm) { struct circular_list *list; if (sa->sa_family == AF_INET) list = confirm >= 2 ? &v4_confirmed : &v4_new; else if (sa->sa_family == AF_INET6) list = confirm >= 2 ? &v6_confirmed : &v6_new; else abort(); /* A node that sends us a request is most probably bootstrapping. We want to avoid getting our tables full of very young nodes -- only include such a node if we have plenty of space. */ if (confirm == 1 && list_free(list) < 32) return false; for (int i = list->head; i != list->tail; i = (i + 1) % CIRCULAR_LIST_SIZE) { struct node *n = &list->nodes[i]; if (n->sslen == salen && memcmp(&n->ss, sa, salen) == 0) return false; } memcpy(&list->nodes[list->tail].id, id, 160); memcpy(&list->nodes[list->tail].ss, sa, salen); list->nodes[list->tail].sslen = salen; list->tail = (list->tail + 1) % CIRCULAR_LIST_SIZE; if (list->head == list->tail) list->head = (list->head + 1) % CIRCULAR_LIST_SIZE; if (verbose) printf("%s : new node\n", getAddr(sa, salen)); return true; } static bool token_bucket(void) { time_t now = time(NULL); if (token_bucket_tokens == 0) { token_bucket_tokens = MIN(MAX_TOKEN_BUCKET_TOKENS, 4 * (int)(now - token_bucket_time)); token_bucket_time = now; } if (token_bucket_tokens == 0) return false; token_bucket_tokens--; return true; } static ssize_t pktSend(const struct Pkt *p) { int s = -1; const struct sockaddr *sa = (const struct sockaddr *)&(p->ss); switch (sa->sa_family) { case AF_INET: s = dht_socket; break; case AF_INET6: s = dht_socket6; break; default: abort(); } return sendto(s, p->buf, p->len, 0, sa, p->salen); } static ssize_t dht_send( const void *buf, const size_t len, const struct sockaddr *sa, const socklen_t salen) { if (salen == 0) abort(); memcpy(&(pkt.ss), sa, sizeof(struct sockaddr_storage)); memcpy(pkt.buf, buf, len); pkt.len = len; pkt.salen = salen; if (sockW == -1) return pktSend(&pkt); return write(sockW, &pkt, sizeof(struct Pkt)); } static ssize_t send_ping( const struct sockaddr *sa, const socklen_t salen, const unsigned char *tid, const size_t tid_len) { char buf[512]; int i = 0; int rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512); COPY(buf, i, myid, 20, 512); rc = snprintf(buf + i, 512 - i, "e1:q4:ping1:t%zu:", tid_len); INC(i, rc, 512); COPY(buf, i, tid, tid_len, 512); ADD_V(buf, i, 512); rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512); if (verbose) printf("%s <- PING\n", getAddr(sa, salen)); return dht_send(buf, (size_t)i, sa, salen); fail: errno = ENOSPC; return -1; } static ssize_t send_pong( const struct sockaddr *sa, const socklen_t salen, const unsigned char *tid, const size_t tid_len) { char buf[512]; int i = 0; int rc = snprintf(buf + i, 512 - i, "d1:rd2:id20:"); INC(i, rc, 512); COPY(buf, i, myid, 20, 512); rc = snprintf(buf + i, 512 - i, "e1:t%zu:", tid_len); INC(i, rc, 512); COPY(buf, i, tid, tid_len, 512); ADD_V(buf, i, 512); rc = snprintf(buf + i, 512 - i, "1:y1:re"); INC(i, rc, 512); if (verbose) printf("%s <- PONG\n", getAddr(sa, salen)); return dht_send(buf, (size_t)i, sa, salen); fail: errno = ENOSPC; return -1; } static ssize_t send_find_node( const struct sockaddr *sa, const socklen_t salen, const unsigned char *tid, const size_t tid_len, const unsigned char *target, const int want) { char buf[512]; int i = 0; int rc = snprintf(buf + i, 512 - i, "d1:ad2:id20:"); INC(i, rc, 512); COPY(buf, i, myid, 20, 512); rc = snprintf(buf + i, 512 - i, "6:target20:"); INC(i, rc, 512); COPY(buf, i, target, 20, 512); if (want > 0) { rc = snprintf( buf + i, 512 - i, "4:wantl%s%se", (want & WANT4) ? "2:n4" : "", (want & WANT6) ? "2:n6" : ""); INC(i, rc, 512); } rc = snprintf(buf + i, 512 - i, "e1:q9:find_node1:t%zu:", tid_len); INC(i, rc, 512); COPY(buf, i, tid, tid_len, 512); ADD_V(buf, i, 512); rc = snprintf(buf + i, 512 - i, "1:y1:qe"); INC(i, rc, 512); if (verbose) printf("%s <- FIND_NODE\n", getAddr(sa, salen)); return dht_send(buf, (size_t)i, sa, salen); fail: errno = ENOSPC; return -1; } static ssize_t send_request( struct circular_list *list, const bool dopop, const bool doping, const int want) { if (list_empty(list)) return 0; struct sockaddr_storage ss; socklen_t sslen; if (dopop) { if (list_pop(list, &ss, &sslen) == 0) return 0; } else { if (list_random(list, NULL, &ss, &sslen) == 0) return 0; } unsigned char ttid[4]; if (doping) { make_tid(ttid, "pn", 0); return send_ping((struct sockaddr *)&ss, sslen, ttid, 4); } else { unsigned char id[20]; arc4random_buf(id, sizeof id); make_tid(ttid, "fn", 0); return send_find_node((struct sockaddr *)&ss, sslen, ttid, 4, id, want); } } static int newSock(const char *host, const char *port) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; struct addrinfo *res = NULL; int rc = getaddrinfo(host, port, &hints, &res); if (rc != 0) err(EXIT_FAILURE, "getaddrinfo: %s\n", gai_strerror(rc)); int sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock == -1) err(EXIT_FAILURE, "socket()"); if (bind(sock, res->ai_addr, res->ai_addrlen) != 0) err(EXIT_FAILURE, "bind()"); rc = fcntl(sock, F_GETFL, 0); if (rc < 0) err(EXIT_FAILURE, "F_GETFL"); rc = fcntl(sock, F_SETFL, (rc | O_NONBLOCK)); if (rc < 0) err(EXIT_FAILURE, "F_SETFL"); freeaddrinfo(res); return sock; } static void rlimited(int res) { struct rlimit r; r.rlim_cur = 0; r.rlim_max = 0; if (setrlimit(res, &r) == -1) { err(EXIT_FAILURE, "setrlimit()"); } } static ssize_t send_nodes( const struct sockaddr *sa, const socklen_t salen, const unsigned char *tid, const size_t tid_len, const unsigned char *nodes, const size_t nodes_len, const unsigned char *nodes6, const size_t nodes6_len) { char buf[2048]; int i = 0; int rc = snprintf(buf + i, 2048 - i, "d1:rd2:id20:"); INC(i, rc, 2048); COPY(buf, i, myid, 20, 2048); if (nodes_len > 0) { rc = snprintf(buf + i, 2048 - i, "5:nodes%zu:", nodes_len); INC(i, rc, 2048); COPY(buf, i, nodes, nodes_len, 2048); } if (nodes6_len > 0) { rc = snprintf(buf + i, 2048 - i, "6:nodes6%zu:", nodes6_len); INC(i, rc, 2048); COPY(buf, i, nodes6, nodes6_len, 2048); } rc = snprintf(buf + i, 2048 - i, "e1:t%zu:", tid_len); INC(i, rc, 2048); COPY(buf, i, tid, tid_len, 2048); ADD_V(buf, i, 2048); rc = snprintf(buf + i, 2048 - i, "1:y1:re"); INC(i, rc, 2048); return dht_send(buf, (size_t)i, sa, salen); fail: errno = ENOSPC; return -1; } static int buffer_random_nodes(int af, unsigned char *nodes) { struct circular_list *list; switch (af) { case AF_INET: list = &v4_confirmed; break; case AF_INET6: list = &v6_confirmed; break; default: abort(); } struct sockaddr_storage ss; socklen_t sslen; unsigned char id[20]; int n = 0; while (n < 8) { if (list_random(list, id, &ss, &sslen) < 1) break; switch (af) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)&ss; memcpy(nodes + n * 26, id, 20); memcpy(nodes + n * 26 + 20, &sin->sin_addr, 4); memcpy(nodes + n * 26 + 24, &sin->sin_port, 2); n++; break; } case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss; memcpy(nodes + n * 38, id, 20); memcpy(nodes + n * 38 + 20, &sin6->sin6_addr, 16); memcpy(nodes + n * 38 + 36, &sin6->sin6_port, 2); n++; break; } default: abort(); } } return n; } static ssize_t send_random_nodes( const struct sockaddr *sa, const socklen_t salen, const unsigned char *tid, const size_t tid_len, int want) { unsigned char nodes[8 * 26]; unsigned char nodes6[8 * 38]; int numnodes = 0; int numnodes6 = 0; if (want < 0) want = sa->sa_family == AF_INET ? WANT4 : WANT6; if ((want & WANT4)) numnodes = buffer_random_nodes(AF_INET, nodes); if ((want & WANT6)) numnodes6 = buffer_random_nodes(AF_INET6, nodes6); if (verbose) printf("%s <- NODES (%d+%d)\n", getAddr(sa, salen), numnodes, numnodes6); return send_nodes( sa, salen, tid, tid_len, nodes, numnodes * 26, nodes6, numnodes6 * 38); } static ssize_t send_error( const struct sockaddr *sa, const socklen_t salen, const unsigned char *tid, const size_t tid_len, const int code, const char *message) { char buf[512]; int i = 0; int rc = snprintf(buf + i, 512 - i, "d1:eli%de%d:", code, (int)strlen(message)); INC(i, rc, 512); COPY(buf, i, message, (int)strlen(message), 512); rc = snprintf(buf + i, 512 - i, "e1:t%zu:", tid_len); INC(i, rc, 512); COPY(buf, i, tid, tid_len, 512); ADD_V(buf, i, 512); rc = snprintf(buf + i, 512 - i, "1:y1:ee"); INC(i, rc, 512); return dht_send(buf, (size_t)i, sa, salen); fail: errno = ENOSPC; return -1; } int main(int argc, char **argv) { errno = 0; char *ipv4addr = NULL; char *ipv6addr = NULL; int opt = 0; for (;;) { opt = getopt(argc, argv, "q4:6:"); if (opt < 0) break; switch (opt) { case 'q': verbose = 0; break; case '4': ipv4addr = strdup(optarg); break; case '6': ipv6addr = strdup(optarg); break; default: goto usage; } } int i = optind; if (argc < i + 1) goto usage; const char *ourPort = strdup(argv[i++]); if (ipv4addr != NULL) { dht_socket = newSock(ipv4addr, ourPort); } if (ipv6addr != NULL) { dht_socket6 = newSock(ipv6addr, ourPort); } arc4random_buf(myid, sizeof myid); memcpy(my_v, "1:v4:JB\0\0", 9); memset(&(pkt.ss), 0, sizeof(struct sockaddr_storage)); int rc = 0; unsigned char ttid[4]; while (i < argc) { struct addrinfo hints, *info, *infop; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; hints.ai_family = 0; if (dht_socket < 0) hints.ai_family = AF_INET6; else if (dht_socket6 < 0) hints.ai_family |= AF_INET; rc = getaddrinfo(argv[i], argv[i + 1], &hints, &info); if (rc != 0) err(EXIT_FAILURE, "getaddrinfo: %s\n", gai_strerror(rc)); i++; if (i >= argc) goto usage; infop = info; while (infop) { make_tid(ttid, "pn", 0); if (send_ping(infop->ai_addr, infop->ai_addrlen, ttid, 4) == -1) err(EXIT_FAILURE, "sendto()"); infop = infop->ai_next; } freeaddrinfo(info); i++; } close(STDIN_FILENO); if (!verbose) close(STDOUT_FILENO); int sockets[2]; if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, sockets) != 0) err(EXIT_FAILURE, "socketpair()"); int sockR = sockets[0]; sockW = sockets[1]; pid_t pid = fork(); if (pid == -1) err(EXIT_FAILURE, "fork()"); if (pid == 0) { close(sockW); rlimited(RLIMIT_NPROC); rlimited(RLIMIT_FSIZE); rlimited(RLIMIT_NOFILE); ssize_t n = 0; unsigned char buf[sizeof(struct Pkt)]; for (;;) { n = read(sockR, buf, sizeof buf); if (n == -1) { perror("read()"); continue; } if (pktSend((struct Pkt *)buf) == -1) perror("sendto()"); } } close(sockR); cap_rights_t caprights; cap_rights_init(&caprights, CAP_WRITE); if (cap_rights_limit(STDERR_FILENO, &caprights) != 0) err(EXIT_FAILURE, "cap_rights_limit(stderr)"); if (!verbose) if (cap_rights_limit(STDOUT_FILENO, &caprights) != 0) err(EXIT_FAILURE, "cap_rights_limit(stdout)"); if (cap_rights_limit(sockW, &caprights) != 0) err(EXIT_FAILURE, "cap_rights_limit(sockW)"); if (cap_enter() != 0) err(EXIT_FAILURE, "cap_enter()"); token_bucket_time = time(NULL); token_bucket_tokens = MAX_TOKEN_BUCKET_TOKENS; int kq = kqueue(); if (kq == -1) err(EXIT_FAILURE, "kqueue()"); struct kevent chs[2]; struct kevent ev; int chsLen = 0; if (dht_socket != -1) { EV_SET(&(chs[chsLen]), dht_socket, EVFILT_READ, EV_ADD, 0, 0, NULL); chsLen++; } if (dht_socket6 != -1) { EV_SET(&(chs[chsLen]), dht_socket6, EVFILT_READ, EV_ADD, 0, 0, NULL); chsLen++; } rlimited(RLIMIT_NPROC); rlimited(RLIMIT_FSIZE); rlimited(RLIMIT_NOFILE); struct timespec tm; bool send4; for (;;) { if ((dht_socket >= 0 && list_elements(&v4_confirmed) <= 16) || (dht_socket6 >= 0 && list_elements(&v6_confirmed) <= 16)) tm.tv_sec = 0; else tm.tv_sec = random() % 30; tm.tv_nsec = 1000000 * (random() % 1000); setproctitle_fast( "%d+%d %d+%d", list_elements(&v4_confirmed), list_elements(&v6_confirmed), list_elements(&v4_new), list_elements(&v6_new)); rc = kevent(kq, chs, chsLen, &ev, 1, &tm); if (rc < 0) err(EXIT_FAILURE, "kevent()"); if (rc > 0) { ssize_t got = -1; unsigned char buf[MAX_PKT_SIZE]; struct sockaddr_storage source_storage; struct sockaddr *source = (struct sockaddr *)&source_storage; socklen_t sourcelen = sizeof(source_storage); if (ev.flags & EV_ERROR) { fprintf(stderr, "EV_ERROR: %s\n", strerror((int)(ev.data))); } else { got = recvfrom((int)ev.ident, buf, MAX_PKT_SIZE, 0, source, &sourcelen); } if (got < 0 || sourcelen > sizeof(struct sockaddr_storage)) goto dontread; if (is_martian(&source_storage)) goto dontread; if (got < MAX_PKT_SIZE) { buf[got] = '\0'; } else { if (verbose) printf("%s : overlong message\n", getAddr(source, sourcelen)); goto dontread; } int message; unsigned char tid[16]; unsigned char id[20]; unsigned char info_hash[20]; unsigned char target[20]; unsigned char nodes[256]; unsigned char nodes6[1024]; unsigned char token[128]; size_t tid_len = sizeof tid; size_t token_len = sizeof token; size_t nodes_len = sizeof nodes; size_t nodes6_len = sizeof nodes6; unsigned short port; unsigned char values[2048]; unsigned char values6[2048]; size_t values_len = sizeof values; size_t values6_len = sizeof values6; int want; message = parse_message( buf, (size_t)got, tid, &tid_len, id, info_hash, target, &port, token, &token_len, nodes, &nodes_len, nodes6, &nodes6_len, values, &values_len, values6, &values6_len, &want, source, sourcelen); if (id_cmp(id, myid) == 0) { if (verbose) printf( "%s : received message from self\n", getAddr(source, sourcelen)); goto dontread; } if (message > REPLY) { /* Rate limit requests. */ if (!token_bucket()) { if (verbose) printf( "%s : dropping request due to rate limiting\n", getAddr(source, sourcelen)); goto dontread; } } switch (message) { case REPLY: if (tid_len != 4) { if (verbose) printf( "%s : broken node truncates transaction ids\n", getAddr(source, sourcelen)); goto dontread; } if (tid_match(tid, "pn", NULL)) { if (verbose) printf("%s -> PONG\n", getAddr(source, sourcelen)); new_node(id, source, sourcelen, 2); } else if (tid_match(tid, "fn", NULL)) { if (verbose) printf( "%s -> NODES (%zu+%zu)\n", getAddr(source, sourcelen), nodes_len / 26, nodes6_len / 38); if (nodes_len % 26 != 0 || nodes6_len % 38 != 0) { if (verbose) printf( "%s : unexpected length for node info\n", getAddr(source, sourcelen)); } else { new_node(id, source, sourcelen, 2); size_t n; for (n = 0; n < nodes_len / 26; n++) { unsigned char *ni = nodes + n * 26; struct sockaddr_in sin; if (id_cmp(ni, myid) == 0) continue; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; memcpy(&sin.sin_addr, ni + 20, 4); memcpy(&sin.sin_port, ni + 24, 2); new_node(ni, (struct sockaddr *)&sin, sizeof(sin), 0); } for (n = 0; n < nodes6_len / 38; n++) { unsigned char *ni = nodes6 + n * 38; struct sockaddr_in6 sin6; if (id_cmp(ni, myid) == 0) continue; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; memcpy(&sin6.sin6_addr, ni + 20, 16); memcpy(&sin6.sin6_port, ni + 36, 2); new_node(ni, (struct sockaddr *)&sin6, sizeof(sin6), 0); } } } else { if (verbose) printf("%s : unexpected reply\n", getAddr(source, sourcelen)); goto dontread; } break; case PING: if (verbose) printf("%s -> PING (%zu)\n", getAddr(source, sourcelen), tid_len); new_node(id, source, sourcelen, 1); send_pong(source, sourcelen, tid, tid_len); break; case FIND_NODE: case GET_PEERS: if (verbose) { if (message == FIND_NODE) printf("%s -> FIND_NODE\n", getAddr(source, sourcelen)); else printf("%s -> GET_PEERS\n", getAddr(source, sourcelen)); } new_node(id, source, sourcelen, 1); send_random_nodes(source, sourcelen, tid, tid_len, want); break; case ANNOUNCE_PEER: if (verbose) printf("%s -> ANNOUNCE_PEER\n", getAddr(source, sourcelen)); send_error( source, sourcelen, tid, tid_len, 203, "This node doesn't accept announces"); break; } } dontread: /* We need to be careful to avoid a positive feedback loop. Make sure we send at most one packet each time through the select loop. */ if (dht_socket6 < 0) send4 = true; else if (dht_socket < 0) send4 = false; else send4 = (random() % 2) == 1; if (send4) { int want = dht_socket6 >= 0 && list_free(&v6_new) > 8 ? (WANT4 | WANT6) : 0; if (!list_empty(&v4_new)) send_request(&v4_new, true, list_free(&v4_new) < 8, want); else if (!list_empty(&v4_confirmed)) send_request(&v4_confirmed, false, false, want); } else { int want = dht_socket >= 0 && list_free(&v4_new) > 8 ? (WANT4 | WANT6) : 0; if (!list_empty(&v6_new)) send_request(&v6_new, true, list_free(&v6_new) < 8, want); else if (!list_empty(&v6_confirmed)) send_request(&v6_confirmed, false, false, want); } } usage: fprintf(stderr, "dht-bootstrap [-q] [-4 ADDR4] [-6 ADDR6] port [node port...]\n"); exit(EXIT_FAILURE); }