diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | makefile | 2 | ||||
-rw-r--r-- | src/bencoding.c | 7 | ||||
-rw-r--r-- | src/dht.c | 157 | ||||
-rw-r--r-- | src/main.c | 56 | ||||
-rw-r--r-- | utils/dns.c | 116 |
6 files changed, 313 insertions, 26 deletions
@@ -7,3 +7,4 @@ a.out core .gdb_history travnik.patch +configfile.ben @@ -3,7 +3,7 @@ CC=cc default: mkdir -p tmp - $(CC) -Wall -Wextra -Wformat -pedantic -g -Isrc -Itmp -Itiny-AES-c $(CFLAGS) src/main.c -otravnik -lm $(LDFLAGS) + $(CC) -fsanitize=address -Wall -Wextra -Wformat -pedantic -g -Isrc -Itmp -Itiny-AES-c $(CFLAGS) src/main.c -otravnik -lm -lresolv $(LDFLAGS) install: mkdir -p $(DESTDIR)/usr/bin/ diff --git a/src/bencoding.c b/src/bencoding.c index 8a044e2..bcf8f17 100644 --- a/src/bencoding.c +++ b/src/bencoding.c @@ -182,13 +182,6 @@ struct bencoding * bnums_set (struct bencoding * b, long int num) { } /** - * helper macros for number comparisons - */ - -#define MAX(x, y) ((x) >= (y) ? (x) : (y)) -#define MIN(x, y) ((x) <= (y) ? (x) : (y)) - -/** * return how much space a character in a string uses * * @param a [in] the character in question @@ -8,8 +8,9 @@ #include <string.h> #include <arpa/inet.h> #include <netinet/ip.h> +#include <arpa/nameser.h> +#include <resolv.h> #include <limits.h> -#include <search.h> #define ECB 1 #define AES128 1 #include <aes.c> @@ -250,7 +251,7 @@ void possible_torrent (struct dht * d __attribute__((unused)), const unsigned ch void sendb (struct dht * d, struct bencoding * b, const struct sockaddr_in6 * a) { char remote[INET6_ADDRSTRLEN + 7]; - if (!inet_ntop(a->sin6_family, a, remote, sizeof *a)) + if (!inet_ntop(a->sin6_family, &a->sin6_addr, remote, INET6_ADDRSTRLEN+7)) snprintf(remote, sizeof remote, "(inet_ntop: %s)", strerror(errno)); sprintf(remote+strlen(remote), ":%d", ntohs(((struct sockaddr_in6 *) a)->sin6_port)); struct bencoding * v = bstr(strdup("TK00")); @@ -476,18 +477,19 @@ struct bencoding * persistent (const struct dht * d) { L(d->log, "getsockname: %s", strerror(errno)); else { struct bencoding * port = bnum(ntohs(bound.sin6_port)); - b->key = bstr(strdup("port")); + port->key = bstr(strdup("port")); binsert(b, port); } #pragma GCC diagnostic pop struct bencoding * nodes = calloc(1, sizeof *nodes); nodes->type = list; + nodes->key = bstr(strdup("nodes")); struct bucket * bucket = d->buckets; while (bucket) { struct node * node = bucket->nodes; while (node) { char remote[INET6_ADDRSTRLEN + 7]; - if (inet_ntop(AF_INET6, &node->addr, remote, sizeof node->addr)) { + if (inet_ntop(AF_INET6, &node->addr.sin6_addr, remote, INET6_ADDRSTRLEN+7)) { sprintf(remote+strlen(remote), "/%u", ntohs(node->addr.sin6_port)); binsert(nodes, bstr(strdup(remote))); } @@ -799,19 +801,25 @@ void replied (const struct dht * d, const unsigned char * id, const struct socka * * see NOTE02 * + * if any of d or id is NULL, it's assumed we don't have this node. this is used for bootstrapping + * * @param d [in] library handle * @param a [in] pointer to sockaddr of the node * @param id [in] id of the node, 20 bytes is read from this address */ void potential_node (struct dht * d, const struct sockaddr_in6 * a, const unsigned char * id) { + if (!id || !d) + goto j; // just do it struct bucket * bucket = d->buckets; if (family(a->sin6_addr.s6_addr) == AF_INET6) bucket = d->buckets6; if (find(id, &bucket, NULL)) return; - if (!bucket_good(d, bucket)) + if (!bucket_good(d, bucket)) { + j: ping_node(d, a); + } } /** @@ -1094,7 +1102,10 @@ void announce_peer (struct dht * d, const struct sockaddr_in6 * addr, struct ben } /** - * handles an incoming packet + * handles an incoming packet, be it: (polyglot packets are technically possible) + * - bencoded packet from another DHT node + * - DNS server response for bootstrapping queries + * - uTP packet from a peer (TODO) * * @param d [in] library handle * @param pkt [in] incoming UDP packet content @@ -1107,7 +1118,7 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { struct bencoding * v = bpath(b, "v"); char * node_ver = ""; char remote[INET_ADDRSTRLEN + INET6_ADDRSTRLEN + 7 + (v && v->type & string) ? v->valuelen : 0]; - if (!inet_ntop(addr.sin6_family, &addr, remote, sizeof addr)) { + if (!inet_ntop(addr.sin6_family, &addr.sin6_addr, remote, INET6_ADDRSTRLEN+7+INET_ADDRSTRLEN)) { snprintf(remote, sizeof remote, "(inet_ntop: %s)", strerror(errno)); sprintf(remote+strlen(remote), ":%d", ntohs(addr.sin6_port)); } @@ -1375,8 +1386,10 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { msg = e->child->next->value; L(d->log, "%s sent %s%s%s", remote, errtype, msg ? ": " : "", msg ? msg : ""); break; + case '\0': // y is not present, this may just be a DNS response, + break; // do not log default: // NOTE01 sending an error is unfortunately bad in this case, since clever hackers can force two servers speaking entirely different UDP based protcols into sending error messages to each other, telling one another that they don't understand each other's messages. - ; + ; // we don't report, since it may be DNS packet int len = b2json_length(b); char json[len+1]; b2json(json, b); @@ -1386,6 +1399,90 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { break; } free_bencoding(b); + ns_msg handle; // parsing incoming DNS packet as utils/dns.c +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" + if (ns_initparse(pkt, len, &handle) != -1) { // queries are sent by periodic +#pragma GCC diagnostic pop + for (int i = 0; i < ns_msg_count(handle, ns_s_an); i++) { + struct __ns_rr rr; + if (ns_parserr(&handle, ns_s_an, i, &rr) == -1) { + L(d->log, "ns_parserr(%s) == -1", remote); + break; + } + if (rr.type != ns_t_srv && rr.type != ns_t_a && rr.type != ns_t_aaaa) { + L(d->log, "%s unknown RR type %d", remote, rr.type); + continue; + } + char address[INET_ADDRSTRLEN+INET6_ADDRSTRLEN+7]; + switch (rr.rdlength) { + case 4: + if (!inet_ntop(AF_INET, rr.rdata, address, INET6_ADDRSTRLEN+INET_ADDRSTRLEN+7)) { + L(d->log, "%s !inet_ntop(AF_INET)", remote); + break; // this can't fail!? + } + sprintf(address+strlen(address), ":%u", ntohs(*((uint16_t *) pkt))); + L(d->log, "%s: A %s", remote, address); + struct sockaddr_in6 a = { + .sin6_family = AF_INET6, + .sin6_port = *((uint16_t *) pkt) + }; + memcpy(a.sin6_addr.s6_addr, "\0\0\0\0\0\0\0\0\xFF\xFF\xFF\xFF", 12); + memcpy(a.sin6_addr.s6_addr+12, rr.rdata, 4); + potential_node(NULL, &a, NULL); + break; + case 16: + if (!inet_ntop(AF_INET6, rr.rdata, address, INET6_ADDRSTRLEN+INET_ADDRSTRLEN+7)) { + L(d->log, "%s !inet_ntop(AF_INET6)", remote); + } + sprintf(address+strlen(address), ":%u", ntohs(*((uint16_t *) pkt))); + L(d->log, "%s: AAAA %s", remote, address); + struct sockaddr_in6 aaaa = { + .sin6_family = AF_INET6, + .sin6_port = *((uint16_t *) pkt) + }; + memcpy(aaaa.sin6_addr.s6_addr, rr.rdata, 16); + potential_node(NULL, &a, NULL); + break; + default: // SRV + if (rr.rdlength < 3*2+3) // . indicates + break; // disabled service - useless + char target[NS_MAXDNAME]; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" + if (ns_name_uncompress(pkt, pkt+len, rr.rdata+3*2, target, NS_MAXDNAME) == -1) { +#pragma GCC diagnostic pop + break; + L(d->log, "ns_name_uncompress(%s) == -1", remote); + } + for (int j = 0; j <= 1; j++) { + struct __res_state state; + if (res_ninit(&state) == -1) { + L(d->log, "res_ninit(%s, %s) == -1", remote, target); + continue; + } + unsigned char packet[65536]; + int size = res_nmkquery(&state, QUERY, target, ns_c_in, j ? ns_t_a : ns_t_aaaa, NULL, 0, NULL, packet, 65536); + if (size == -1) { + L(d->log, "res_nmkquery(%s) == -1", target); + goto d; + } + memcpy(packet, rr.rdata+4, 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + if (sendto(d->socket, packet, size, MSG_DONTWAIT | MSG_NOSIGNAL, &addr, sizeof addr) == -1) + L(d->log, "sendto(%s, %s)", remote, target); + d->txp++; + d->txb += size; +#pragma GCC diagnostic pop +d: + res_nclose(&state); + } + break; + + } + } + } // do not log, it may have been a bencoded reply } /** @@ -1396,7 +1493,7 @@ void handle (struct dht * d, char * pkt, int len, struct sockaddr_in6 addr) { */ int refresh (struct bucket * b) { - int good = 0; + int nrgood = 0; while (b) { struct node ** n = &b->nodes; while (*n) { @@ -1411,14 +1508,14 @@ int refresh (struct bucket * b) { // 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; case good: - good++; + nrgood++; break; } n = &(*n)->next; } b = b->next; } - return good; + return nrgood; } /** @@ -1477,6 +1574,8 @@ void get_peers (struct dht * d, const struct sockaddr_in6 * addr, const unsigned * - searching deeper DHT storage nodes for torrents with peers and announce * - get_peers on torrents with peers or announce. when a response is received for a torrent with announce, an announce will be sent as well * + * for bootstrapping, an **IPv4** nameserver is required in /etc/resolv.conf. res_*() functions only provide IPv4 nameservers. example script with this algorithm is available in utils/dns.c + * * this can be a lot of packets, so please keep number of torrents with peers and announce low */ @@ -1487,9 +1586,35 @@ void periodic (struct dht * d) { if (!refresh(d->buckets6)) dns++; if (dns) { - - // perform a query to SRV travnik.sijanec.eu, continue with parsing in handle() + char packet[65536]; + struct __res_state state; + if (res_ninit(&state) == -1) { + L(d->log, "res_ninit(&state) == -1"); + goto t; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" + int size = res_nmkquery(&state, QUERY, "_dht._udp.travnik.sijanec.eu", ns_c_in, ns_t_srv, NULL, 0, NULL, packet, 65536) != -1; +#pragma GCC diagnostic pop + if (size == -1) { + L(d->log, "res_nmkquery(SRV) == -1"); + goto d; + } +#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, size, MSG_DONTWAIT | MSG_NOSIGNAL, &state.nsaddr_list[i], sizeof state.nsaddr_list[i]) == -1) + L(d->log, "sendto: %s", strerror(errno)); + d->txp++; + d->txb += size; + } +#pragma GCC diagnostic pop + d: + res_nclose(&state); // receiving and resolving SRV->{A,AAAA} in handle() } + t: + ; struct torrent * t = d->torrents; while (t) { if (t->type & (peers | announce)) { @@ -1501,14 +1626,14 @@ void periodic (struct dht * d) { n = n->next; } if (sent < K) { -#define RTGP(buckets) struct bucket * b = d->buckets; \ +#define RTGP(buckets) {struct bucket * b = d->buckets; \ find(t->hash, &b, NULL); \ struct node * n = b->nodes; \ while (sent < K && n) { \ sent++; \ get_peers(d, &n->addr, t->hash); \ - n = n->next; - } + n = n->next; \ + }} RTGP(buckets); RTGP(buckets6); } @@ -1,7 +1,59 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <error.h> +#include <fcntl.h> +#include <signal.h> +#define S0(x) (x ? x : "") #include <dht.c> -int main (void) { - struct dht * dht = dht_init(); +int main (int argc, char ** argv) { + int r = 0; + if (argc != 1+1) + error_at_line(1, 0, __FILE__, __LINE__, "%s configfile.ben", S0(argv[0])); + int cf = open(argv[1], O_RDWR | O_CLOEXEC | O_CREAT, 00664); + if (cf == -1) + error_at_line(2, errno, __FILE__, __LINE__, "open(%s)", argv[1]); + struct stat statbuf; + if (fstat(cf, &statbuf) == -1) { + error_at_line(0, errno, __FILE__, __LINE__, "fstat(cf, &statbuf)"); + r = 3; + goto r; + } + char * cfr = NULL; + if (statbuf.st_size && !(cfr = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, cf, 0))) { + error_at_line(0, errno, __FILE__, __LINE__, "mmap(NULL, %ld, PROT_READ, MAP_SHARED, cf, 0)", statbuf.st_size); + r = 4; + goto r; + } + struct bencoding * config = bdecode(cfr, statbuf.st_size, replace); + struct dht * dht = dht_init(config); + free_bencoding(config); + config = persistent(dht); + dht_free(dht); + if (cfr && munmap(cfr, statbuf.st_size) == -1) { + error_at_line(0, errno, __FILE__, __LINE__, "munmap(cf, %ld)", statbuf.st_size); + r = 105; + goto r; + } + cfr = NULL; + if (ftruncate(cf, (statbuf.st_size = bencode_length(config))) == -1) { + error_at_line(0, errno, __FILE__, __LINE__, "ftruncate(cf, %ld)", statbuf.st_size); + r = 106; + goto r; + } + if (!(cfr = mmap(NULL, statbuf.st_size, PROT_WRITE, MAP_SHARED, cf, 0))) { + error_at_line(0, errno, __FILE__, __LINE__, "mmap(NULL, %ld, PROT_READ, MAP_SHARED, cf, 0)", statbuf.st_size); + r = 107; + goto r; + } + bencode(cfr, config); + free_bencoding(config); + r: + if (cfr && munmap(cfr, statbuf.st_size) == -1) + error_at_line(0, errno, __FILE__, __LINE__, "munmap(cf, %ld)", statbuf.st_size); + if (close(cf) == -1) + error_at_line(0, errno, __FILE__, __LINE__, "close(cf)"); + return r; } diff --git a/utils/dns.c b/utils/dns.c new file mode 100644 index 0000000..e365e73 --- /dev/null +++ b/utils/dns.c @@ -0,0 +1,116 @@ +#include <stdio.h> // example of an asyncronous resolver that resolves +#include <stdlib.h> // a SRV record to obtain address:port combinations +#include <sys/socket.h> // of useful nodes +#include <netinet/in.h> // BUGS: does not handle CNAMES - this is +#include <arpa/nameser.h> // technically against the standard, but would be +#include <resolv.h> // nice if the domain pointed to by the SRV record +#include <arpa/inet.h> // travnik.sijanec.eu suddenly becomes a CNAME, +#include <error.h> // since it's not under my control +#include <errno.h> +#include <unistd.h> +#include <sys/poll.h> +#include <string.h> +#include <signal.h> +#define S0(x) (x ? x : "") +int main (int argc, char ** argv) { // does not free/close on error + alarm(1); + if (argc != 1+1) + error_at_line(1, 0, __FILE__, __LINE__, "%s: _dht._udp.travnik.sijanec.eu", S0(argv[0])); + struct __res_state state; + if (res_ninit(&state) == -1) + error_at_line(2, 0, __FILE__, __LINE__, "res_ninit"); + int sock = socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (!sock) + error_at_line(3, errno, __FILE__, __LINE__, "socket"); + struct sockaddr_in6 a = { + .sin6_family = AF_INET6, + .sin6_addr = in6addr_any + }; + if (bind(sock, (struct sockaddr *) &a, sizeof a) == -1) + error_at_line(4, errno, __FILE__, __LINE__, "bind"); + unsigned char packet[65536]; + int size = res_nmkquery(&state, QUERY, argv[1], ns_c_in, ns_t_srv, NULL, 0, NULL, packet, 65536); + if (size == -1) + error_at_line(5, 0, __FILE__, __LINE__, "res_mkquery"); +#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 only ipv4 + if (sendto(sock, packet, size, MSG_DONTWAIT | MSG_NOSIGNAL, &state.nsaddr_list[i], sizeof (state.nsaddr_list[i])) == -1) + error_at_line(6, errno, __FILE__, __LINE__, "sendto(AF_INET)"); +/* for (int i = 0; i < state._u._ext.nscount6; i++) + if (sendto(sock, packet, size, MSG_DONTWAIT | MSG_NOSIGNAL, &state._u._ext.nsaddrs[i], sizeof (state._u._ext.nsaddrs[i])) == -1) + error_at_line(7, errno, __FILE__, __LINE__, "sendto(AF_INET6)"); */ // does not work +#pragma GCC diagnostic pop + struct pollfd pollfd = { + .fd = sock, + .events = POLLIN + }; + r: + ; + int status = poll(&pollfd, 1, -1); + if (status == 1) { + socklen_t l = sizeof a; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + int len = recvfrom(sock, packet, 65536, MSG_DONTWAIT | MSG_TRUNC, &a, &l); +#pragma GCC diagnostic pop + if (len == -1) + error_at_line(8, errno, __FILE__, __LINE__, "recvfrom"); + ns_msg handle; + char remote[INET6_ADDRSTRLEN+7]; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + if (!inet_ntop(AF_INET6, &a.sin6_addr, remote, INET6_ADDRSTRLEN+7)) + error_at_line(9, errno, __FILE__, __LINE__, "inet_pton"); +#pragma GCC diagnostic pop + sprintf(remote+strlen(remote), ":%u", ntohs(a.sin6_port)); + if (ns_initparse(packet, len, &handle) == -1) + error_at_line(10, 0, __FILE__, __LINE__, "ns_initparse %s", remote); + for (int i = 0; i < ns_msg_count(handle, ns_s_an); i++) { + struct __ns_rr rr; + if (ns_parserr(&handle, ns_s_an, i, &rr) == -1) + break; + if (rr.type != ns_t_srv && rr.type != ns_t_a && rr.type != ns_t_aaaa) + continue; + char target[NS_MAXDNAME]; + char address[INET_ADDRSTRLEN+INET6_ADDRSTRLEN+7]; + switch (rr.rdlength) { + case 4: + if (!inet_ntop(AF_INET, rr.rdata, address, INET6_ADDRSTRLEN+INET_ADDRSTRLEN+7)) + error_at_line(11, errno, __FILE__, __LINE__, "inet_ntop(AF_INET)"); + sprintf(address+strlen(address), ":%u", ntohs(*((uint16_t *) packet))); + printf("%s\tA\t%s\n", remote, address); + break; + case 16: + if (!inet_ntop(AF_INET6, rr.rdata, address, INET6_ADDRSTRLEN+INET_ADDRSTRLEN+7)) + error_at_line(12, errno, __FILE__, __LINE__, "inet_ntop(AF_INET6)"); + sprintf(address+strlen(address), ":%u", ntohs(*((uint16_t *) packet))); + printf("%s\tAAAA\t%s\n", remote, address); + break; + default: + if (rr.rdlength < 3*2+3) + continue; + if (ns_name_uncompress(packet, packet+len, rr.rdata+3*2, target, NS_MAXDNAME) == -1) + error_at_line(13, 0, __FILE__, __LINE__, "ns_name_uncompress %s", remote); + // printf("%s\tSRV\t%u\t%s\n", remote, htons(*((uint16_t *) (rr.rdata + 4))), target); + for (int j = 0; j <= 1; j++) { + size = res_nmkquery(&state, QUERY, target, ns_c_in, j ? ns_t_a : ns_t_aaaa, NULL, 0, NULL, packet, 65536); + if (size == -1) + error_at_line(14, 0, __FILE__, __LINE__, "res_mkquery(A)"); + memcpy(packet, rr.rdata+4, 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + if (sendto(sock, packet, size, MSG_DONTWAIT | MSG_NOSIGNAL, &a, l) == -1) + error_at_line(15, errno, __FILE__, __LINE__, "sendto"); + } + break; + } +#pragma GCC diagnostic pop + } + goto r; + } + res_nclose(&state); + if (close(sock) == -1) + error_at_line(16, errno, __FILE__, __LINE__, "close"); +} |