From 458ca31cc617831dbe05d129fffa5e023c06d3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Tue, 10 Jan 2023 11:53:56 +0100 Subject: metadl works, but wastes bandwidth --- README.md | 3 +- makefile | 2 +- src/dht.c | 341 ++++++++++++++++++++++++++++++++++-------------------- src/main.c | 33 ++++-- src/tcp.c | 2 +- www/.gitignore | 3 + www/composer.json | 14 +++ www/index.php | 77 ++++++++++++ 8 files changed, 334 insertions(+), 141 deletions(-) create mode 100644 www/.gitignore create mode 100644 www/composer.json create mode 100644 www/index.php diff --git a/README.md b/README.md index 457dcda..0e04d85 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ # todo * use ppoll * implement BEP-0042 DHT security extension, requires crc32c library -* fix announcements +* metadata receiving from uTorrent does not work for some unknown reason +* uses A LOT of bandwidth, implement packet deduplication # known non-posix * `MSG_DONTWAIT`, `SOCK_NONBLOCK`: replace with `fcntl` `O_NONBLOCK` diff --git a/makefile b/makefile index 534a876..35826df 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ DESTDIR=/ CC=cc -MYCFLAGS=-Wall -Wextra -Wformat -pedantic -g -Isrc -Itmp -Itiny-AES-c -fsanitize=address +MYCFLAGS=-DHARDCODED_DNS=`(host ipv6.sijanec.eu || echo no_host_program_on_build_host) | tail -n1 | rev | cut -d\ -f1 | rev` -Wall -Wextra -Wformat -pedantic -g -Isrc -Itmp -Itiny-AES-c # -fsanitize=address MYLDFLAGS=-lresolv -lmd default: diff --git a/src/dht.c b/src/dht.c index 6998cfb..467b036 100644 --- a/src/dht.c +++ b/src/dht.c @@ -13,10 +13,13 @@ #include #include #include // http://github.com/clibs/sha1 and in major distributions +#include #define ECB 1 #define AES128 1 #include #include +#define STRX(x) #x +#define STR(x) STRX(x) time_t seconds (void) { struct timespec tp; @@ -87,10 +90,13 @@ struct node { * creates a new node */ +unsigned node_init_count = 0; + struct node * node_init (void) { struct node * n = calloc(1, sizeof *n); if (!n) return NULL; + node_init_count++; n->last_received = seconds(); n->addr.sin6_family = AF_INET6; return n; @@ -102,7 +108,10 @@ struct node * node_init (void) { * @param n [in] the node to be freed */ +unsigned node_free_count = 0; + void node_free (struct node * n) { + node_free_count++; free(n); } @@ -313,6 +322,8 @@ void disconnect (struct torrent * t) { close(t->socket); t->socket = -1; t->state = 0; + if (t->dl) + t->dl->flags &= ~(requested); t->dl = NULL; t->size = 0; t->progress = 0; @@ -441,11 +452,11 @@ struct dht { struct bucket * buckets; struct bucket * buckets6; /**< IPv6 routing table */ struct torrent * torrents; /**< linked list of torrents for which we want to know peers */ - void (* possible_torrent)(struct dht *, const unsigned char *); /**< a user callback function that is called whenever we come across a torrent hash from a network */ + void (* possible_torrent)(struct dht *, const unsigned char *, struct torrent *); /**< a user callback function that is called whenever we come across a torrent hash from a network. the library may signal that this torrent is in storage by including a pointer to it in the call, but this is not required. the torrent may be stored, but it wasn't reported to be stored since when we came across the hash we didn't need to look the torrent up. but if the torrent pointer is non NULL, it's definitely stored */ #ifdef DHT_USERDATA DHT_USERDATA #else - void * userdata; /**< unused, but left for the library user to set so he can refer back to his structures from callback code, such as dht->possible_torrent(d, h) */ + void * userdata; /**< unused, but left for the library user to set so he can refer back to his structures from callback code, such as dht->possible_torrent(d, h, t) */ #endif unsigned torrents_num; /**< number of torrents. this number can rise indefinitely, so it can, and should be capped by the caller, depending on available memory */ unsigned peers_num; /**< number of peers. same notice regarding memory applies here as for torrents */ @@ -463,6 +474,10 @@ struct dht { unsigned tt; /**< tcp transmitted bytes */ unsigned tr; /**< tcp received bytes */ enum verbosity verbosity; /**< what to log */ +#ifdef SAMPLE + unsigned char sample[60000]; /**< for sample infohashes */ + int samples; /**< for sample infohashes, max 3000 */ +#endif }; /** @@ -480,12 +495,6 @@ void dht_print (FILE * s, const struct dht * d) { secret[17*2+1] = '\0'; bin2hex(secret, d->secret, 16); fprintf(s, "id=%s socket=%d t=%u p=%u tmax=%u pmax=%u p/t-max=%u runsec=%ld rxp=%u txp=%u rxb=%u txb=%u secret=%s tt=%u tr=%u\n", buf, d->socket, d->torrents_num, d->peers_num, d->torrents_max, d->peers_max, d->peers_per_torrent_max, seconds()-d->time, d->rxp, d->txp, d->rxb, d->txb, secret, d->tt, d->tr); - printf("**** TORRENTS ****\n"); - struct torrent * t = d->torrents; - while (t) { - torrent_print(s, t); - t = t->next; - } fprintf(s, "**** NODES ****\n"); int nodes = 0; for (int i = 0; i <= 1; i++) { @@ -510,6 +519,12 @@ void dht_print (FILE * s, const struct dht * d) { fprintf(s, "\t**** COUNT OF %s BUCKETS: %d\n", i ? "IPv4" : "IPV6", buckets); } fprintf(s, "**** COUNT OF NODES: %d\n", nodes); + printf("**** TORRENTS ****\n"); + struct torrent * t = d->torrents; + while (t) { + torrent_print(s, t); + t = t->next; + } } /** @@ -519,7 +534,7 @@ void dht_print (FILE * s, const struct dht * d) { * @param h [in] the infohash of the found torrent */ -void possible_torrent (struct dht * d __attribute__((unused)), const unsigned char * h __attribute__((unused))) { +void possible_torrent (struct dht * d __attribute__((unused)), const unsigned char * h __attribute__((unused)), struct torrent * t __attribute__((unused))) { return; } @@ -699,19 +714,20 @@ struct dht * dht_init (const struct bencoding * c) { if (id && id->type & string && id->valuelen == 20) memcpy(d->id, id->value, 20); bforeach (bpath(c, "nodes"), str) { - struct sockaddr_in6 addr = { - .sin6_family = AF_INET6 - }; char remote[INET6_ADDRSTRLEN + 7]; - strncpy(remote, str->value, str->valuelen); + if (str->valuelen > INET6_ADDRSTRLEN+6) + continue; + strcpy(remote, str->value); char * port = strchr(remote, '/'); + if (!port) + continue; port[0] = '\0'; - if (port) { - if (inet_pton(AF_INET6, remote, addr.sin6_addr.s6_addr) == 1) { - addr.sin6_port = htons(atoi(++port)); - ping_node(d, &addr); - } - } + struct sockaddr_in6 addr = { + .sin6_family = AF_INET6, + .sin6_port = htons(atoi(++port)) + }; + if (inet_pton(AF_INET6, remote, addr.sin6_addr.s6_addr) == 1) + ping_node(d, &addr); } } return d; @@ -884,25 +900,56 @@ int in_bucket (const unsigned char * id, const struct bucket * b) { * @param b pointer to a variable containing a pointer to the first bucket in ll. after the call the value at this pointer is overwritten to the bucket that should contain this node. since it's overwritten, do NOT just pass &dht->buckets. do struct bucket * b = dht->buckets and pass &b instead. * @param n [out] the node directly before the searched for node. NULL is written if this node would be placed at the start of the bucket. NULL may be passed without consequences. * @return the pointer to the node or NULL if not found - */ + */ -struct node * find (const unsigned char * id, struct bucket ** b, struct node ** n) { +/* __attribute__((deprecated)) */ struct node * find (const unsigned char * id, struct bucket ** b, struct node ** n) { while (!in_bucket(id, *b)) *b = (*b)->next; struct node * node = (*b)->nodes; - struct node * prev = NULL; + struct node * prev; + if (!n) + n = &prev; + *n = NULL; while (node && memcmp(node->id, id, 20) < 0) { - prev = node; + *n = node; node = node->next; } - if (n) - *n = prev; if (node && !memcmp((node)->id, id, 20)) return node; else return NULL; } +/** + * search for a node in a linked list of nodes. + * + * @param id [in] the node id + * @param first [in] first node in LL + * @return either location of pointer to the searched for node or the location where this node may be inserted. + */ + +struct node ** search_node (const unsigned char * id, struct node ** first) { + if (!(*first)->next) + return first; + if (memcmp((*first)->next->id, (*first)->id, 20) >= 0) + return first; + return search_node(id, &(*first)->next); +} + +/** + * search for a node in bucket list + * + * @param id [in] the node id + * @param first [io] variable holding pointer to first bucket in LL, *WILL BE OVERWRITTEN* with bucket that should contain node + * @param either location of pointer to the searched for node or the location where this node may be inserted. + */ + +struct node ** search_bucket (const unsigned char * id, struct bucket ** first) { + if (!in_bucket(id, *first)) + return search_bucket(id, &(*first)->next); + return search_node(id, &(*first)->nodes); +} + /** * add two 20 byte numbers * @@ -1105,9 +1152,6 @@ int bucket_good (const struct dht * d, const struct bucket * b) { void replied (const struct dht * d, const unsigned char * id, const struct sockaddr_in6 * addr) { if (!memcmp(d->id, id, 20)) // WE COULDN'T'VE POSSIBLY REPLIED TO OURSELVES! return; - char buf[41]; - buf[40] = '\0'; - bin2hex(buf, id, 20); struct bucket * b = d->buckets; if (family(addr->sin6_addr.s6_addr) == AF_INET6) b = d->buckets6; @@ -1118,13 +1162,13 @@ void replied (const struct dht * d, const unsigned char * id, const struct socka found->unanswered = 0; return; } - if (bucket_good(d, b)) { + if (bucket_good(d, b)) return; - } struct node * node = node_init(); memcpy(&node->addr, addr, sizeof *addr); memcpy(node->id, id, 20); if (!n) { + node->next = b->nodes; b->nodes = node; return; } @@ -1136,8 +1180,8 @@ void replied (const struct dht * d, const unsigned char * id, const struct socka index->next = node; return; } + node_free(node); if (in_bucket(d->id, b)) { - node_free(node); split(b); replied(d, id, addr); // find bucket again } @@ -1257,6 +1301,12 @@ struct torrent * add_torrent (struct dht * d, struct torrent * t) { t->next = d->torrents; d->torrents = t; d->torrents_num++; +#ifdef SAMPLE + if (d->samples < 3000) + memcpy(d->sample+20*d->samples++, t->hash, 20); + else + memcpy(d->sample+20*(rand() % 3000), t->hash, 20); +#endif if (d->torrents_num >= d->torrents_max) oom(d); return t; @@ -1376,81 +1426,49 @@ void get_peers (struct dht * d, const struct sockaddr_in6 * addr, const unsigned /** * called from compact() on response to get_peers, this function sends a get_peers to a node if it would improve the node pool of a torrent * - * @param d [in] library handle. if NULL, get_peers is not sent and it's expected that caller will insert a new node into torrent (because one good will be removed if the potential node is closer and there's no bad node and nodelist is full) + * @param d [in] library handle * @param t [in] torrent * @param a [in] address of node * @param i [in] 20 byte id of node - * @return the position to which the new node should be written. the caller, if overwrites the pointed memory with this address, must update ->next of inserted node to the value that this address pointed to before */ -struct node ** potential_torrent_node (struct dht * d, struct torrent * t, const struct sockaddr_in6 * a, const unsigned char * i) { +void potential_torrent_node (struct dht * d, struct torrent * t, const struct sockaddr_in6 * a, const unsigned char * i) { if (!t->nodes) { - if (d) - get_peers(d, a, t->hash); - return &t->nodes; + get_peers(d, a, t->hash); + return; } - int l = 0; struct node * n = t->nodes; - struct node ** rs = NULL; // return if enough space - struct node ** farthest = &t->nodes; - struct node * prev = NULL; + struct node * badnode = NULL; + struct node * farthest = t->nodes; + unsigned l = 0; while (n) { l++; - if (node_grade(n) == bad) { - if (prev) - prev->next = n->next; - else - t->nodes = n->next; - node_free(n); - l--; - if (prev) - n = prev->next; - else - n = t->nodes; - continue; - } - int cp = memcmp(n->id, i, 20); - if (!cp) - return NULL; - if (!n->next) - rs = &n->next; - else { - int cn = memcmp(i, n->next->id, 20); - if (!cn) - return NULL; - if (cp == 1 && cn == -1) - rs = &n->next; - } - if (!closer(n->id, (*farthest)->id, t->hash)) { - if (prev) - farthest = &t->nodes; - else - farthest = &prev->next; - } - prev = n; + if (node_grade(n) == bad) + badnode = n; + if (!closer(n->id, farthest->id, t->hash)) + farthest = n; + if (!memcmp(n->id, i, 20)) + return; n = n->next; } - if (l < K) { - if (d) - get_peers(d, a, t->hash); - return rs; + if (!closer(i, farthest->id, t->hash)) { + get_peers(d, a, t->hash); + return; } - if (!closer(i, (*farthest)->id, t->hash)) { - if (!d) { - node_free(*farthest); - *farthest = prev; - if (d) - get_peers(d, a, t->hash); - return farthest; - } + if (badnode) { + get_peers(d, a, t->hash); + return; + } + if (l < K) { + get_peers(d, a, t->hash); + return; } - return NULL; } /** * called on a response to get_peers, the node that responded is added to a list of good nodes of a torrent if it's a potential torrent node, if it's already in the list, it's stats are updated. * - * note that torrent nodelists have no housekeeping like the routing table has with refresh(), so bad nodes are removed only in potential_torrent_node. + * note that torrent nodelists have no housekeeping like the routing table has with refresh(), so bad nodes are removed only when adding a new one. * * @param t [in] torrent * @param a [in] address of node @@ -1458,23 +1476,44 @@ struct node ** potential_torrent_node (struct dht * d, struct torrent * t, const */ void replied_torrent_node (struct torrent * t, const struct sockaddr_in6 * a, const unsigned char * i) { - struct node * existing = t->nodes; - int r; - while (existing && (r = memcmp(existing->id, i, 20)) < 0) - existing = existing->next; - if (!r) { - existing->last_received = seconds(); - existing->unanswered = 0; + struct node ** n = &t->nodes; + struct node ** badnode = NULL; + struct node ** farthest = &t->nodes; + unsigned l = 0; + while (*n) { + l++; + if (node_grade(*n) == bad) + badnode = n; + if (!closer((*n)->id, (*farthest)->id, t->hash)) + farthest = n; + if (!memcmp((*n)->id, i, 20)) { + (*n)->unanswered = 0; + (*n)->last_received = seconds(); + return; + } + n = &(*n)->next; + } + struct node * new = node_init(); + memcpy(new->id, i, 20); + memcpy(&new->addr, a, sizeof new->addr); + if (l < K) { + new->next = t->nodes; + t->nodes = new; return; } - struct node ** dst = potential_torrent_node(NULL, t, a, i); - if (!dst) + if (badnode) { + new->next = (*badnode)->next; + node_free(*badnode); + *badnode = new; return; - struct node * n = node_init(); - memcpy(n->id, i, 20); - memcpy(&n->addr, a, sizeof n->addr); - n->next = *dst; - *dst = n; + } + if (!closer(i, (*farthest)->id, t->hash)) { + new->next = (*farthest)->next; + node_free(*farthest); + *farthest = new; + return; + } + node_free(new); } /** @@ -1672,6 +1711,12 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { L(disagreement, d, "%s did not send a valid id in %s", remote, j); } switch (qtype[0]) { +#ifdef SAMPLE + case 'S': // sample_infohashes + case 's': // oops, we don't do that. this would be a huge DDoS amplification possibility. +#error SAMPLE not implemented because it would be an amplification DDoS vector + break; +#endif case 'P': // ping case 'p': ; @@ -1735,12 +1780,6 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { struct bencoding * hash = bpath(b, "a/info_hash"); if (!hash || !(hash->type & string) || hash->valuelen != 20) break; // see NOTE01 - else { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpointer-sign" - d->possible_torrent(d, hash->value); -#pragma GCC diagnostic pop - } response = calloc(1, sizeof *response); response->type = dict; y = bstr(strdup("r")); @@ -1771,18 +1810,25 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpointer-sign" struct torrent * torrent = find_torrent(d, hash->value, 20); + d->possible_torrent(d, hash->value, torrent); #pragma GCC diagnostic pop + unsigned i = 0; if (torrent) { struct peer * peer = torrent->peers; struct bencoding * values = calloc(1, sizeof *values); values->type = list; while (peer) { // TODO implement peer preference: prefer sending peers that responded to us - if (family(peer->addr.sin6_addr.s6_addr) == family(addr.sin6_addr.s6_addr)) { // possible - struct bencoding * value = calloc(1, sizeof *value); - memcpy((value->value = malloc((value->valuelen = ADDRLEN(family(peer->addr.sin6_addr.s6_addr))+2))), peer->addr.sin6_addr.s6_addr, ADDRLEN(family(peer->addr.sin6_addr.s6_addr))); - memcpy(value->value+ADDRLEN(family(peer->addr.sin6_addr.s6_addr)), &peer->addr.sin6_port, 2); - binsert(values, value); // possible stack overflow if there are a lot of peers, see limit in bdecode() wrapper - } // TODO add a random IP address for plausible deniability + if (family(peer->addr.sin6_addr.s6_addr) != family(addr.sin6_addr.s6_addr)) // possible + goto c; + if (peer->flags & unreachable) + goto c; + if (i++ > K) + break; + struct bencoding * value = calloc(1, sizeof *value); + memcpy((value->value = malloc((value->valuelen = ADDRLEN(family(peer->addr.sin6_addr.s6_addr))+2))), peer->addr.sin6_addr.s6_addr, ADDRLEN(family(peer->addr.sin6_addr.s6_addr))); + memcpy(value->value+ADDRLEN(family(peer->addr.sin6_addr.s6_addr)), &peer->addr.sin6_port, 2); + binsert(values, value); + c: peer = peer->next; } binsert(r, values); @@ -1803,8 +1849,10 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { tok = bpath(b, "a/token"); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpointer-sign" - if (!tok || !(tok->type & string) || tok->valuelen != 16 || !valid(d, tok->value, addr.sin6_addr.s6_addr)) + if (!tok || !(tok->type & string) || tok->valuelen != 16 || !valid(d, tok->value, addr.sin6_addr.s6_addr)) { + L(disagreement, d, "invalid announce token from %s", remote); break; // see NOTE01 + } #pragma GCC diagnostic pop hash = bpath(b, "a/info_hash"); if (!hash || !(hash->type & string) || hash->valuelen != 20) @@ -1829,6 +1877,10 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { torrent = torrent_init(); // gucci, because add_torrent returns existing and frees if already stored memcpy(torrent->hash, hash->value, 20); torrent = add_torrent(d, torrent); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" + d->possible_torrent(d, hash->value, torrent); +#pragma GCC diagnostic pop struct peer * peer = peer_init(); // same as with torrent memcpy(&peer->addr, &addr, sizeof addr); if (bpath(b, "a/port") && (!bpath(b, "a/implied_port") || !bpath(b, "a/implied_port")->intvalue)) @@ -1953,7 +2005,7 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { L(std_fail, d, "%s !inet_ntop(AF_INET)", remote); break; // this can't fail!? } - sprintf(address+strlen(address), ":%u", ntohs(*((uint16_t *) pkt))); + sprintf(address+strlen(address), "/%u", ntohs(*((uint16_t *) pkt))); L(expected, d, "%s: A %s", remote, address); struct sockaddr_in6 a = { .sin6_family = AF_INET6, @@ -1966,7 +2018,7 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { case 16: if (!inet_ntop(AF_INET6, rr.rdata, address, INET6_ADDRSTRLEN+INET_ADDRSTRLEN+7)) L(std_fail, d, "%s !inet_ntop(AF_INET6)", remote); - sprintf(address+strlen(address), ":%u", ntohs(*((uint16_t *) pkt))); + sprintf(address+strlen(address), "/%u", ntohs(*((uint16_t *) pkt))); L(expected, d, "%s: AAAA %s", remote, address); struct sockaddr_in6 aaaa = { .sin6_family = AF_INET6, @@ -2034,7 +2086,7 @@ int refresh (struct bucket * b) { struct node * old = *n; *n = (*n)->next; node_free(old); - break; + continue; case questionable: // ping_node(d, *n); // NOTE03 about not pinging questionable nodes: this ensures a constant regeneration of the routing table. this is just an idea, if the client frequently gets in a situation without any nodes in the routing table, remove the comment before ping_node call. break; @@ -2080,6 +2132,7 @@ void periodic (struct dht * d) { dns++; if (dns) { char packet[512]; + memset(packet, '\0', 512); struct __res_state state; if (res_ninit(&state) == -1) { L(std_fail, d, "res_ninit(&state) == -1"); @@ -2093,16 +2146,39 @@ void periodic (struct dht * d) { L(std_fail, d, "res_nmkquery(SRV) == -1"); goto d; } + for (int i = 0; i < state.nscount; i++) + if (state.nsaddr_list[i].sin_family == AF_INET) { // leider only ipv4 NSs #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wincompatible-pointer-types" - for (int i = 0; i < state.nscount; i++) - if (state.nsaddr_list[i].sin_family == AF_INET) { // leider if (sendto(d->socket, packet, 512, MSG_DONTWAIT | MSG_NOSIGNAL, &state.nsaddr_list[i], sizeof state.nsaddr_list[i]) == -1) L(std_fail, d, "sendto: %s", strerror(errno)); +#pragma GCC diagnostic pop d->txp++; d->txb += size; } +#ifdef HARDCODED_DNS // because ipv6 dns is not supported + struct sockaddr_in6 hc = { + .sin6_family = AF_INET6, + .sin6_port = htons(53) + }; + int ret = inet_pton(AF_INET6, STR(HARDCODED_DNS), hc.sin6_addr.s6_addr); + switch (ret) { + case 0: + L(disagreement, d, "hardcoded dns server " STR(HARDCODED_DNS) " is invalid"); + break; + case 1: + L(expected, d, "querying hardcoded dns server " STR(HARDCODED_DNS)); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + if (sendto(d->socket, packet, 512, MSG_DONTWAIT | MSG_NOSIGNAL, &hc, sizeof hc) == -1) + L(std_fail, d, "sendto hardcoded dns: %s", strerror(errno)); #pragma GCC diagnostic pop + break; + case -1: + L(std_fail, d, "inet_pton(" STR(HARDCODED_DNS) "): %s", strerror(errno)); + break; + } +#endif d: res_nclose(&state); // receiving and resolving SRV->{A,AAAA} in handle() } @@ -2149,7 +2225,7 @@ void periodic (struct dht * d) { } } } - if (t->type & info && t->peers) { + if (t->type & info) { if (t->dl) { if (seconds() - t->time > DLTO) { t->dl->flags |= unreachable; @@ -2164,6 +2240,8 @@ void periodic (struct dht * d) { c++; p = p->next; } + if (!c) + goto a; int s = rand() % c; // OB1 untested p = t->peers; while (p) { @@ -2194,6 +2272,7 @@ void periodic (struct dht * d) { free(t->packet); t->packet = malloc(32727); t->recvd = 0; + t->dl->flags &= ~(requested); d->connection(d, t); t->intentions(t); break; @@ -2233,6 +2312,7 @@ void tcp_work (struct dht * d) { } else if (errno != EAGAIN) { L(std_fail, d, "send(): %s", strerror(errno)); + t->dl->flags |= unreachable; disconnect(t); goto c; } @@ -2275,12 +2355,15 @@ void tcp_work (struct dht * d) { if (!(t->state & ~(handshake_sent | incoming | outgoing))) { int ret = recv(t->socket, packet, 1+19+8+20*2, MSG_DONTWAIT); if (ret == 0) { - L(disagreement, d, "received 0 bytes instead of handshake? weird. waiting"); + L(disagreement, d, "received 0 bytes instead of handshake. EOF"); + t->dl->flags |= nometasupport; + disconnect(t); goto c; } if (ret < 0) { if (errno != EAGAIN) { L(std_fail, d, "recv(TCP, MSG_PEEK): %s (%d)", strerror(errno), errno); + t->dl->flags |= unreachable; disconnect(t); } goto c; @@ -2299,7 +2382,7 @@ void tcp_work (struct dht * d) { if (memcmp(packet+1+19+8, t->hash, 20)) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpointer-sign" - possible_torrent(d, packet+1+19+8); + possible_torrent(d, packet+1+19+8, NULL); #pragma GCC diagnostic pop disconnect(t); goto c; @@ -2321,7 +2404,7 @@ void tcp_work (struct dht * d) { goto c; } else { uint32_t l = ntohl(*((uint32_t *) t->packet)); - L(debug, d, "found length of a packet to be %d", l); + L(debug, d, "found length of a packet to be %u", l); d->tr += ret; t->recvd += ret; } @@ -2434,9 +2517,8 @@ void tcp_work (struct dht * d) { int piecelen = t->recvd-((ee+2)-t->packet); L(debug, d, "received a %d byte piece", piecelen); if (t->size < t->progress*16384+piecelen) { - raise(SIGINT); - disconnect(t); L(disagreement, d, "sent more packets than space available. UNREACHABLE!!!"); + disconnect(t); break; } if (!t->metadata) { @@ -2448,12 +2530,17 @@ void tcp_work (struct dht * d) { memcpy(t->metadata+t->progress++*16384, ee+2, piecelen); t->time = seconds(); if (t->progress*16384 >= t->size) { - SHA1_CTX sha; // TODO SHA256 for torrent v2 + SHA1_CTX sha; uint8_t results[SHA1_DIGEST_LENGTH]; SHA1Init(&sha); SHA1Update(&sha, t->metadata, t->size); SHA1Final(results, &sha); - if (memcmp(results, t->hash, 20)) { + SHA2_CTX ctx; + uint8_t results2[SHA256_DIGEST_LENGTH]; + SHA256Init(&ctx); + SHA256Update(&ctx, t->metadata, t->size); + SHA256Final(results2, &ctx); + if (memcmp(results, t->hash, 20) && memcmp(results2, t->hash, 20)) { t->dl->flags |= badmeta; t->dl->flags &= ~goodmeta; L(disagreement, d, "received invalid metadata!"); diff --git a/src/main.c b/src/main.c index d6c9fb3..7527921 100644 --- a/src/main.c +++ b/src/main.c @@ -12,7 +12,7 @@ #define TORRENT_USERDATA struct dht * dht; #define DHT_USERDATA struct pollfd ** pollfds; size_t * pollfds_size; nfds_t * nfds; #include -#define DISCONNECTION_MIXIN_BOTTOM if (t->dl->flags & goodmeta) t->type &= ~info; +#define DISCONNECTION_MIXIN_BOTTOM if (t->dl->flags & goodmeta) t->type &= ~(info | peers); #include int samomor = 0; int periodično = 0; @@ -31,11 +31,20 @@ void handler (int s) { break; } } -void found_torrent (struct dht * d __attribute__((unused)), const unsigned char * h) { - char buf[41]; +void found_torrent (struct dht * d __attribute__((unused)), const unsigned char * h, struct torrent * t) { + char buf[128]; bin2hex(buf, h, 20); buf[40] = '\0'; - L(user, d, "magnet:?xt=urn:btih:%s", buf); + L(debug, d, "magnet:?xt=urn:btih:%s%s", buf, t ? " stored" : ""); + if (t) { + struct stat statbuf; + strcat(buf, ".torrent"); + if (!stat(buf, &statbuf)) { + L(expected, d, "%s already exists", buf); + return; + } else + t->type |= info | peers; + } } int main (int argc, char ** argv) { size_t pollfds_size = 1; @@ -66,13 +75,16 @@ int main (int argc, char ** argv) { error_at_line(8, errno, __FILE__, __LINE__, "sigaddset(SIGTERM)"); if (sigprocmask(SIG_UNBLOCK, &sigset, NULL) == -1) error_at_line(9, errno, __FILE__, __LINE__, "sigprocmask"); - /* struct itimerval itimerval = { + struct itimerval itimerval = { .it_interval = { - .tv_sec = 60 + .tv_sec = PERIODIC + }, + .it_value = { + .tv_sec = PERIODIC } }; if (setitimer(ITIMER_REAL, &itimerval, NULL) == -1) - error_at_line(9, errno, __FILE__, __LINE__, "setitimer"); */ + error_at_line(9, errno, __FILE__, __LINE__, "setitimer"); if (argc != 1+1) error_at_line(10, 0, __FILE__, __LINE__, "%s configfile.ben", S0(argv[0])); int cf = open(argv[1], O_RDWR | O_CLOEXEC | O_CREAT, 00664); @@ -107,7 +119,7 @@ int main (int argc, char ** argv) { torrent->type = /* (useless, since we have no listening system yet) announce | */ peers | info; add_torrent(dht, torrent); periodic(dht); - alarm(PERIODIC); + // alarm(PERIODIC); w: while (poll(pollfds, nfds, -1) != -1) // can't timeout work(dht); @@ -120,7 +132,7 @@ w: } if (periodično) { periodično = 0; - alarm(PERIODIC); + // alarm(PERIODIC); periodic(dht); goto w; } @@ -131,7 +143,6 @@ w: break; default: error_at_line(0, errno, __FILE__, __LINE__, "poll"); - raise(SIGINT); r = 115; goto r; } @@ -168,6 +179,6 @@ w: error_at_line(0, errno, __FILE__, __LINE__, "close(cf)"); if (pollfds) free(pollfds); - fprintf(stderr, "exiting cleanly with status %d\n", r); + fprintf(stderr, "exiting cleanly with status %d node_init_count=%u node_free_count=%u\n", r, node_init_count, node_free_count); return r; } diff --git a/src/tcp.c b/src/tcp.c index 99f8828..bfd49bc 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -44,7 +44,7 @@ void intentions (struct torrent * t) { if (t->state & outgoing) (*t->dht->pollfds)[i].events |= POLLOUT; } - fprintf(stderr, "peer's intentions: "); + fprintf(stderr, "tcp intentions: "); peer_print(stderr, t->dl); fprintf(stderr, " nfds=%ld, pollfds=%zu%s%s\n", *t->dht->nfds, *t->dht->pollfds_size, (t->state & incoming) ? " reading" : "", (t->state & outgoing) ? " writing" : ""); } diff --git a/www/.gitignore b/www/.gitignore new file mode 100644 index 0000000..8796548 --- /dev/null +++ b/www/.gitignore @@ -0,0 +1,3 @@ +package-lock.json +default +vendor/ diff --git a/www/composer.json b/www/composer.json new file mode 100644 index 0000000..ed23328 --- /dev/null +++ b/www/composer.json @@ -0,0 +1,14 @@ +{ + "name": "sijanec/travnik", + "description": "web frontend for exploring metainfo files downloaded by travnik", + "type": "project", + "require": { + "rhilip/bencode": "^2.3" + }, + "authors": [ + { + "name": "Anton Luka Šijanec", + "email": "anton@sijanec.eu" + } + ] +} diff --git a/www/index.php b/www/index.php new file mode 100644 index 0000000..2fd5a92 --- /dev/null +++ b/www/index.php @@ -0,0 +1,77 @@ +"; + while (false !== ($entry = readdir($handle))) { + if (preg_match("/torrent$/", $entry)) { + $h = htmlspecialchars(explode(".", $entry)[0]); + echo '
  • ' . $h . ''; + } + } + die(); + closedir($handle); + } else { + die("ne morem brati direktorija"); + } +} +if (!preg_match("/^[a-f0-9A-F]{40}$/", $_REQUEST["h"])) + die('!preg_match("/^[a-f0-9A-F]{40}$/", $_REQUEST["h"])'); +$t = TorrentFile::load("../".$_REQUEST["h"].".torrent"); +?> + + +

    getName()) ?>

    +<?= htmlspecialchars($t->getName()) ?> + + + + + + + + + + + isPrivate()) echo ""; ?> + + + + + + + + + + + + + +getRootData()["source"]["v"]) { ?> + + + + +
    pridobljenogetCreationDate()) ?>
    tipgetProtocol() ?>
    datotečni načingetFileMode() ?>
    zaseben
    magnetna povezava
    .torrent>torrent datoteka
    velikostgetSize()/(1024*1024*1024), 6, ",", "") ?> GiB
    število datotekgetFileCount() ?>
    ip naslov viragetRootData()["source"]["ip"]) ?>
    odjemalec viragetRootData()["source"]["v"]) ?>
    + " . htmlspecialchars($k) . "
      "; + foreach ($v as $ke => $va) + p($ke, $va); + echo "
    "; + } else { + echo "
  • " . htmlspecialchars($k) . " (" . number_format($v/(1024*1024), 6, ",", "") . " MiB)"; + } +} +echo "
      "; +foreach ($t->getFileTree() as $k => $v) + p($k, $v); +echo "
    "; +?> -- cgit v1.2.3