diff options
| author | Jesper Jensen <jesper@slashwin.dk> | 2021-08-28 22:27:24 +0200 |
|---|---|---|
| committer | Jesper Jensen <jesper@slashwin.dk> | 2021-08-28 22:27:24 +0200 |
| commit | e00efb4381c5123699e4ef9807b67eba54ea06a0 (patch) | |
| tree | df2d7567a20e2313d576fb00aab61be8ee3383fa /src | |
| parent | de3bec63c63b0c30a5a4e1d9b4a5478984b888e9 (diff) | |
Initial fanout implemented
Diffstat (limited to 'src')
| -rw-r--r-- | src/benc.c | 232 | ||||
| -rw-r--r-- | src/benc.h | 31 | ||||
| -rw-r--r-- | src/log.h | 20 | ||||
| -rw-r--r-- | src/main.c | 360 | ||||
| -rw-r--r-- | src/routing.c | 22 | ||||
| -rw-r--r-- | src/routing.h | 13 |
6 files changed, 647 insertions, 31 deletions
diff --git a/src/benc.c b/src/benc.c new file mode 100644 index 0000000..4f61ff7 --- /dev/null +++ b/src/benc.c @@ -0,0 +1,232 @@ +#include "benc.h" +#include "log.h" + +#include <errno.h> +#include <stdlib.h> +#include <assert.h> +#include <ctype.h> +#include <string.h> + + +#define MAX(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; \ + }) + +#define MIN(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; \ + }) + +bool readint(const char** loc, int64_t* val) { + char* end = NULL; + errno = 0; + *val = strtol(*loc, &end, 10); + *loc = end; + + return errno == 0; +} + +bool digit(const char c) { + return c >= '0' && c <= '9'; +} + +char* nodenames[] = { + "INT", + "STR", + "LIST", + "DICT", + "END", +}; + +void indent(int depth) { + for(int i = 0; i < depth; i++) { + printf(" "); + } +} + +void benc_print(const struct benc_node* stream, size_t stream_len, int* depth) { + const struct benc_node* cursor = stream; + for(; cursor < stream + stream_len; cursor++) { + switch(cursor->type) { + case BNT_INT: + indent(*depth); + printf("INT %.*s\n", cursor->size, cursor->loc); + break; + case BNT_STRING: + indent(*depth); + bool allprint = true; + for (const char* c = cursor->loc; c < cursor->loc + cursor->size; c++) { + if(!isalnum(*c)) { + allprint = false; + break; + } + } + printf("STR "); + if(allprint) { + printf("%.*s", cursor->size, cursor->loc); + } else { + for (const unsigned char* c = (const unsigned char*)cursor->loc; c < (unsigned char*)(cursor->loc + cursor->size); c++) { + printf("\\x%02X", *c); + } + } + printf("\n"); + break; + case BNT_LIST: + indent(*depth); + printf("LIST\n"); + *depth = cursor->depth + 1; + break; + case BNT_DICT: + indent(*depth); + printf("DICT\n"); + *depth = cursor->depth + 1; + break; + case BNT_END: + *depth = cursor->depth; + indent(*depth); + printf("END\n"); + break; + } + } +} + +int64_t benc_decode(const char** cursor, const char* end, int* depth, struct benc_node* stream, size_t stream_len) { + size_t cursor_out = 0; + + while(cursor_out < stream_len) { + struct benc_node* node = &stream[cursor_out]; + + if(**cursor == 'i') { + node->type = BNT_INT; + (*cursor)++; + if(*cursor >= end) { + return -cursor_out; + } + node->loc = *cursor; + while(**cursor != 'e') { + if(**cursor != '-' && !digit(**cursor)) { + return -cursor_out; + } + (*cursor)++; + if(*cursor >= end) { + return -cursor_out; + } + } + node->size = *cursor - node->loc; + (*cursor)++; + } else if(**cursor == 'l') { + node->type = BNT_LIST; + node->depth = *depth; + (*depth)++; + node->loc = *cursor; + (*cursor)++; + if(*cursor >= end) { + return -cursor_out; + } + } else if(**cursor == 'd') { + node->type = BNT_DICT; + node->depth = *depth; + (*depth)++; + node->loc = *cursor; + (*cursor)++; + if(*cursor >= end) { + return -cursor_out; + } + } else if(**cursor == 'e') { + node->type = BNT_END; + (*depth)--; + node->depth = *depth; + node->loc = *cursor; + (*cursor)++; + } else if(digit(**cursor)) { + node->type = BNT_STRING; + int64_t val; + bool rc = readint(cursor, &val); + node->size = val; + if(*cursor >= end) { + return -cursor_out; + } + assert(rc); + if(**cursor != ':') { + return -cursor_out; + } + (*cursor)++; + if(*cursor >= end) { + return -cursor_out; + } + node->loc = *cursor; + (*cursor) += node->size; + if(*cursor > end) { + return -cursor_out; + } + } else { + return -cursor_out; + dbg("Failing on char \"%c\"", **cursor); + assert(false); + } + cursor_out++; + if(*depth == 0) break; + } + return cursor_out; +} + +void skip_sibling(const struct benc_node** cursor, const struct benc_node* end) { + assert((*cursor)->type == BNT_LIST || (*cursor)->type == BNT_DICT); + + if((*cursor)->type == BNT_LIST || (*cursor)->type == BNT_DICT) { + uint32_t tdepth = (*cursor)->depth; + (*cursor)++; + while(true) { + if(*cursor == end) { + fatal("Invalid dict"); + } + + if((*cursor)->type == BNT_END && (*cursor)->depth == tdepth) { + break; + } + + (*cursor)++; + } + (*cursor)++; + } else { + (*cursor)++; + } +} + +// All the arrays should be equal length +ssize_t skip_to_key(const struct benc_node** cursor, const struct benc_node* end, const enum benc_nodetype* keyTypes, const char** keyValues, const size_t* keyLengths, const size_t keys) { + // @ROBUSTNESS: Check if keytypes are anything but strings and ints because that is not supported + + while(true) { + if(*cursor > end-1) { + fatal("Invalid dict"); + } + + // Skip lists + if((*cursor)->type == BNT_LIST || (*cursor)->type == BNT_DICT) { + skip_sibling(cursor, end); + } else { + for(size_t i = 0; i < keys; i++) { + if((*cursor)->type == keyTypes[i] && memcmp((*cursor)->loc, keyValues[i], MIN((*cursor)->size, keyLengths[i])) == 0) { + return i; + } + } + } + + (*cursor) += 2; + + if((*cursor-1)->type == BNT_LIST || (*cursor-1)->type == BNT_DICT) { + (*cursor)--; + skip_sibling(cursor, end); + } + + if((*cursor)->type == BNT_END) { + return -1; + } + } +} diff --git a/src/benc.h b/src/benc.h new file mode 100644 index 0000000..a5734ea --- /dev/null +++ b/src/benc.h @@ -0,0 +1,31 @@ +#pragma once + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> +#include <unistd.h> + +enum benc_nodetype { + BNT_INT, + BNT_STRING, + BNT_LIST, + BNT_DICT, + BNT_END, +}; + +struct benc_node { + enum benc_nodetype type; + const char* loc; + union { + size_t size; + uint32_t depth; + }; +}; + +bool readint(const char** loc, int64_t* val); + +void benc_print(const struct benc_node* stream, size_t stream_len, int* depth); +int64_t benc_decode(const char** cursor, const char* end, int* depth, struct benc_node* stream, size_t stream_len); + +void skip_sibling(const struct benc_node** cursor, const struct benc_node* end); +ssize_t skip_to_key(const struct benc_node** cursor, const struct benc_node* end, const enum benc_nodetype* keyTypes, const char** keyValues, const size_t* keyLengths, const size_t keys); diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..1717011 --- /dev/null +++ b/src/log.h @@ -0,0 +1,20 @@ +#pragma once + +#include <stdio.h> + +#define dbg(format, ...) \ + dbgl(format "\n", ## __VA_ARGS__) + +#define dbgl(format, ...) \ + printf(format, ## __VA_ARGS__); \ + fflush(stderr) + +#define err(format, ...) \ + printf(format "\n", ## __VA_ARGS__); \ + fflush(stderr) + +#define fatal(format, ...) \ + printf(format "\n", ## __VA_ARGS__); \ + fflush(stderr); \ + abort() + @@ -1,8 +1,11 @@ #include "routing.h" +#include "benc.h" +#include "log.h" #include <errno.h> #include <sys/stat.h> #include <fcntl.h> +#include <ctype.h> #include <assert.h> #include <stdlib.h> #include <stdint.h> @@ -10,6 +13,26 @@ #include <unistd.h> #include <stdbool.h> #include <string.h> +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/types.h> +#include <stdio.h> + +#define MAX(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; \ + }) + +#define MIN(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; \ + }) void dbgl_id(struct nodeid* id) { @@ -20,35 +43,334 @@ void dbgl_id(struct nodeid* id) { fflush(stderr); } +struct discovery { + uint16_t port; + struct nodeid expected_id; +}; + +int sockaddr_cmp(struct sockaddr* x, struct sockaddr* y) { +#define CMP(a, b) \ + do { \ + typeof(a) cmp = a - b; \ + if(cmp != 0) return cmp; \ + } while(0) + if (x->sa_family == AF_INET) { + struct sockaddr_in *xin = (void*)x; + struct sockaddr_in *yin = (void*)y; + + CMP(ntohl(xin->sin_addr.s_addr), ntohl(yin->sin_addr.s_addr)); + CMP(ntohs(xin->sin_port), ntohs(yin->sin_port)); + } else if (x->sa_family == AF_INET6) { + struct sockaddr_in6 *xin6 = (void*)x, *yin6 = (void*)y; + int r = memcmp(xin6->sin6_addr.s6_addr, yin6->sin6_addr.s6_addr, sizeof(xin6->sin6_addr.s6_addr)); + if (r != 0) + return r; + CMP(ntohs(xin6->sin6_port), ntohs(yin6->sin6_port)); + CMP(xin6->sin6_flowinfo, yin6->sin6_flowinfo); + CMP(xin6->sin6_scope_id, yin6->sin6_scope_id); + } else { + err("Unsupported sa_family"); + abort(); + } + + return 0; +}; + +#define UNDEF_ADDR (struct in_addr){0xC0000200} +#define MAX_DISC 32 +struct in_addr addrs[MAX_DISC]; +struct discovery pending_discover[MAX_DISC]; + +typedef void (*cont)(struct nodeid* self, char* packet, size_t packet_len, int socket, struct sockaddr_in* remote, socklen_t remote_len); + +#define MAX_INFLIGHT 32 +bool reqalloc[MAX_INFLIGHT] = { false }; +struct { + struct sockaddr addr; + cont fun; +} requestdata[MAX_INFLIGHT]; + +bool alloc_req(uint16_t* reqId) { + for(size_t i = 0; i < MAX_INFLIGHT; i++) { + if(!reqalloc[i]) { + reqalloc[i] = true; + *reqId = i; + return true; + } + } + return false; +} + +bool find_req(uint32_t transId, uint16_t* reqId) { + *reqId = transId; + return reqalloc[transId]; +} + +void getclient_response(struct nodeid* self, char* packet, size_t packet_len, int socket, struct sockaddr_in* remote, socklen_t remote_len); + +int send_ping(struct nodeid* self, const int sfd, const struct sockaddr* dest_addr, socklen_t dest_len) { + uint16_t reqId; + if(!alloc_req(&reqId)) { + return ENOBUFS; + } + char buff[128]; + size_t i = 0; + + dbg("Allocating request %d", reqId); + requestdata[reqId].fun = &getclient_response; + requestdata[reqId].addr = *dest_addr; + + int rc = snprintf(buff+i, 128-i, "d1:ad2:id20:"); + i += rc; + memcpy(buff+i, self, sizeof(struct nodeid)); + i += sizeof(struct nodeid); + rc = snprintf(buff+i, 128-i, "6:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t%d:%d1:y1:qe", (reqId/10)+1, reqId); + i += rc; + + //now reply the client with the same data + rc = sendto(sfd, buff, i, 0, dest_addr, dest_len); + if (rc == -1) { + switch(errno) { + case EWOULDBLOCK: case EBADF: + return rc; + } + return EPERM; // Operation not permitted is used as the default "generic" error + } + + return 0; +} + +void getclient_response(struct nodeid* self, char* packet, size_t packet_len, int socket, struct sockaddr_in* remote, socklen_t remote_len) { + struct benc_node stream[256]; + const char* cursor = packet; + int depth = 0; + int len = benc_decode(&cursor, packet+packet_len, &depth, stream, 256); + + if(len <= 0) { + err("Reponse too short"); + exit(EXIT_FAILURE); + } + + struct nodeid id; + uint8_t nodes_len; + struct nodeid nodes[8]; + struct in_addr ips[8]; + uint16_t ports[8]; + + + // Read the payload + { + // Check that we have a dict + struct benc_node* cursor = stream; + if(cursor->type != BNT_DICT) { + err("Response is not a dict"); + exit(EXIT_FAILURE); + } + cursor++; + + skip_to_key((const struct benc_node**)&cursor, stream+len, (const enum benc_nodetype[]){BNT_STRING}, (const char*[]){"r"}, (const size_t[]){1}, 1); + // Skip the key + cursor++; + + if(cursor->type != BNT_DICT) { + err("Wrong value type for response"); + exit(EXIT_FAILURE); + } + + // Skip the dict element + cursor++; + + while(cursor->type != BNT_END) { + switch(skip_to_key((const struct benc_node**)&cursor, stream+len, (const enum benc_nodetype[]){BNT_STRING, BNT_STRING}, (const char*[]){"nodes", "id"}, (const size_t[]){5, 2}, 2)) { + case 0: + // Skip the key + cursor++; + + if(cursor->type != BNT_STRING) { + err("Wrong value type for response"); + exit(EXIT_FAILURE); + } + + if((cursor->size % 26) != 0) { + err("get_nodes call returned an incorrect nodes array"); + exit(EXIT_FAILURE); + } + + nodes_len = MIN(cursor->size/26, 8); + dbg("We have %d (%d/26) nodes", nodes_len, cursor->size); + for(int i = 0; i < nodes_len; i++) { + memcpy(nodes+i, cursor->loc+(26*i), 20); + memcpy(ips+i, cursor->loc+(26*i)+20, 4); + memcpy(ports+i, cursor->loc+(26*i)+24, 2); + } + + // Skip the value + cursor++; + break; + case 1: + // Skip the key + cursor++; + + if(cursor->type != BNT_STRING) { + err("Wrong value type for response"); + exit(EXIT_FAILURE); + } + + if(cursor->size != 20) { + err("remote node id was not 20 bytes long"); + exit(EXIT_FAILURE); + } + + memcpy(&id, cursor->loc, 20); + + // Skip the value + cursor++; + break; + } + assert(cursor < stream+len); + } + } + + // Print the candidates + for(uint8_t i = 0; i < nodes_len; i++) { + dbgl_id(&nodes[i]); + printf("Candidate %s:%d\n", inet_ntoa(ips[i]), ntohs(ports[i])); + + struct sockaddr_in dest = { + .sin_family = AF_INET, + .sin_addr = ips[i], + .sin_port = ports[i], + }; + + if(routing_interested(&nodes[i])) { + int rc = send_ping(self, socket, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)); + if(rc != 0) { + err("send_ping failed %d", rc); + } + } else { + dbg("Not interested in node"); + } + } + + struct entry* entry; + routing_offer(&id, &entry); +} + int main(int argc, char** argv) { + for(int i = 0; i < MAX_DISC; i++) { + // 192.0.2.0 + addrs[i] = UNDEF_ADDR; + } + + struct nodeid self = {.inner={0x0034048f, 0x08000020, 0x00888880, 0x02008460, 0x0ab00521}}; + routing_init(&self); + dbgl_id(&self); - struct nodeid a = {0}; - a.inner[1] = 0xFFFFFFFF; - routing_init(&a); + int sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(sfd == -1) { + err("Failed creating socket"); + exit(1); + } + struct sockaddr_in bindAddr = {0}; + bindAddr.sin_family = AF_INET; + bindAddr.sin_port = htons(6881); + bindAddr.sin_addr.s_addr = htonl(INADDR_ANY); + bind(sfd, (struct sockaddr*)&bindAddr, sizeof(struct sockaddr_in)); + struct addrinfo hints = {0}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_NUMERICSERV; - struct nodeid b = {0}; - b.inner[1] = 0xFFFFFFFF; - b.inner[4] = 0x00000001; - struct entry *entry; - if(routing_offer(&b, &entry)) { - entry->addr.ip = 0; - entry->addr.port = 0; + struct addrinfo* res; + int rc = getaddrinfo("router.bittorrent.com", "6881", &hints, &res); + if(rc != 0) { + err("Failed getting the bootstrap ip: %s", gai_strerror(rc)); + exit(EXIT_FAILURE); } - b.inner[1] = 0xFFFFFFFF; - b.inner[4] = 0x00000002; - if(routing_offer(&b, &entry)) { - entry->addr.ip = 0; - entry->addr.port = 0; + for(struct addrinfo* cur = res; cur != NULL; cur = cur->ai_next) { + char buff[128]; + inet_ntop(cur->ai_family, &((struct sockaddr_in*)cur->ai_addr)->sin_addr, buff, 128); + dbg("IP %s", buff); + + /* size_t i = 0; */ + /* int rc = snprintf(buff+i, 128-i, "d1:ad2:id20:"); */ + /* i += rc; */ + /* memcpy(buff+i, &self, sizeof(struct nodeid)); */ + /* i += sizeof(struct nodeid); */ + /* rc = snprintf(buff+i, 128-i, "e1:q4:ping1:t2:ab1:y1:qe"); */ + /* i += rc; */ + + send_ping(&self, sfd, cur->ai_addr, cur->ai_addrlen); } - struct entry *found[10]; - size_t nfound = routing_closest(&b, 10, found); + freeaddrinfo(res); + + //keep listening for data + while(1) { + char buff[2049]; + printf("Waiting for data..."); + fflush(stdout); + + //try to receive some data, this is a blocking call + ssize_t recv_len; + struct sockaddr_in remote; + socklen_t remote_len = sizeof(remote); + if ((recv_len = recvfrom(sfd, buff, 2048, 0, (struct sockaddr *)&remote, &remote_len)) == -1) { + fatal("recvfrom failed"); + } + if(recv_len >= 2048) { + fatal("Recv buffer too small"); + } + // Null terminate the packet + buff[recv_len] = '\0'; - for(size_t i = 0; i < nfound; i++) { - dbgl_id(&found[i]->id); + printf("Received packet from %s:%d\n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port)); + + struct benc_node stream[256]; + const char* cursor = buff; + int depth = 0; + int len = benc_decode(&cursor, buff+recv_len, &depth, stream, 256); + benc_print(stream, len, &depth); + + struct benc_node* stream_cursor = stream; + if(stream_cursor->type != BNT_DICT) { + err("Response is not a dict"); + exit(EXIT_FAILURE); + } + stream_cursor++; + + if(skip_to_key((const struct benc_node**)&stream_cursor, stream+len, (const enum benc_nodetype[]){BNT_STRING}, (const char*[]){"t"}, (const size_t[]){1}, 1) == -1) { + err("No t key in packet"); + exit(EXIT_FAILURE); + } + stream_cursor++; + + char char_buffer = stream_cursor->loc[stream_cursor->size]; + ((char*)stream_cursor->loc)[stream_cursor->size] = '\0'; + uint32_t transaction = strtol(stream_cursor->loc, NULL, 10); + ((char*)stream_cursor->loc)[stream_cursor->size] = char_buffer; + + uint16_t reqId; + if(!find_req(transaction, &reqId)) { + err("No request with transaction ID %d", transaction); + exit(EXIT_FAILURE); + } + dbg("Transaction id matches requst %d", reqId); + + if(sockaddr_cmp(&requestdata[reqId].addr, (struct sockaddr*)&remote) != 0) { + err("Unexpected IP for valid transaction"); + exit(EXIT_FAILURE); + } + + requestdata[reqId].fun(&self, buff, recv_len, sfd, &remote, remote_len); + + reqalloc[reqId] = false; } + close(sfd); + return 0; } diff --git a/src/routing.c b/src/routing.c index ff7eadc..b8ab037 100644 --- a/src/routing.c +++ b/src/routing.c @@ -82,6 +82,28 @@ int8_t scan(uint16_t baseIndex, struct nodeid* id) { return index; } +bool routing_interested(struct nodeid* id) { + uint16_t bucketIndex = prefix(&myID, id); + // The nodeid is the same as our own + if(bucketIndex == IDBITS) { + return false; + } + + // If they are sufficiently similar they end up in the final bucket. Clamp the index to ensure. + bucketIndex = bucketIndex > (IDBITS - BUCKETBITS) ? (IDBITS - BUCKETBITS) : bucketIndex; + assert(bucketIndex <= IDBITS - BUCKETBITS); + + uint16_t baseIndex = bucketIndex * BUCKETSIZE; + int8_t inBucketIndex = scan(baseIndex, id); + + if(inBucketIndex == -1) { + // The bucket either already contains the node, or it has no more space + return false; + } + + return true; +} + // Offer the routing table a new node bool routing_offer(struct nodeid* id, struct entry **dest) { uint16_t bucketIndex = prefix(&myID, id); diff --git a/src/routing.h b/src/routing.h index b0e9922..540f3f6 100644 --- a/src/routing.h +++ b/src/routing.h @@ -6,18 +6,6 @@ #include <time.h> #include <stdio.h> -#define dbg(format, ...) \ - dbgl(format "\n", ## __VA_ARGS__) - -#define dbgl(format, ...) \ - printf(format, ## __VA_ARGS__); \ - fflush(stderr) - -#define err(format, ...) \ - printf(format "\n", ## __VA_ARGS__); \ - fflush(stderr) - - struct addr { uint32_t ip; uint16_t port; @@ -36,5 +24,6 @@ struct entry { void routing_init(struct nodeid* myid); void routing_flush(); +bool routing_interested(struct nodeid* id); bool routing_offer(struct nodeid* id, struct entry **dest); size_t routing_closest(struct nodeid* needle, size_t n, struct entry** res); |
