From 51b913eb2ff91adfe8cd5e417759a6057c5a8a21 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Thu, 13 Nov 2008 16:17:55 +0000 Subject: XBitTorrent --- tracker.c | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 tracker.c (limited to 'tracker.c') diff --git a/tracker.c b/tracker.c new file mode 100644 index 0000000..599b214 --- /dev/null +++ b/tracker.c @@ -0,0 +1,398 @@ +/* $Id$ */ +/* + * Copyright (c) 2005 Dimitri Sokolyuk + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include /* htons() */ +#include +#include /* AF_INET */ +#include +#include +#include /* inet_ntoa() */ +#include + +#include +#include +#include +#include +#include /* getpagesize() */ + +#include "bencode.h" +#include "meta.h" +#include "peer.h" +#include "tracker.h" +#include "tools.h" +#include "version.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +#if FAILURE +/* ref: http://wiki.theory.org/BitTorrentTrackerProtocol */ +static const struct { + int code; + char *msg; +} failure[] = { + { 100, "Invalid Request Type" }, + { 101, "Missing `info_hash'" }, + { 102, "Missing `peer_id'" }, + { 103, "Missing `port'" }, + { 150, "Invalid `info_hash'" }, + { 151, "Invalid `peer_id'" }, + { 152, "Invalid `numwait'" }, + { 200, "`info_hash' not found in database" }, + { 500, "Client sent an event request before the specified time" }, + { 900, "Generic Error" }, +}; +#endif + +static int parseanswer(struct bttracker *, char *); +static int parseplist(struct bttracker *, struct btnode *); +static char *buildrequest(struct bttracker *); +static int fillsin(char *, int, struct sockaddr_in *); +static int startlistener(int *); + +static int +parseanswer(struct bttracker *tp, char *src) +{ + struct btnode *nhead, *np; + + src = strstr(src, "\r\n\r\n") + 4; + + if ((nhead = btbencode(src)) == NULL) + errx(1, "fatal error: %s\n", src); + + if ((np = btfinddict(nhead, "failure reason")) != NULL) + errx(1, "failure reason: %s\n", np->str); + +#if FAILURE + if ((np = btfinddict(nhead, "failure code")) != NULL) { + int i; + for (i = 0; failure[i].code != 900; i++) + if (np->num == failure[i].code) + break; + errx(1, failure[i].msg); + } +#endif + + if ((np = btfinddict(nhead, "warning message")) != NULL) + warnx("warning message: %s\n", np->str); + + if ((np = btfinddict(nhead, "interval")) != NULL) + tp->interval = np->num; + + if ((np = btfinddict(nhead, "min interval")) != NULL) + tp->minterval = np->num; + + if ((np = btfinddict(nhead, "tracker id")) != NULL) + memcpy(tp->trackerid, np->str, SHA1LEN); + + if ((np = btfinddict(nhead, "complete")) != NULL) + tp->complete = np->num; + + if ((np = btfinddict(nhead, "incomplete")) != NULL) + tp->incomplete = np->num; + + if ((np = btfinddict(nhead, "peers")) != NULL) + parseplist(tp, np); + +#if 0 /* removing dead peers is senseless; + collect all peers until we finish downloading */ + tp->npeers = btclearoldpeers(&tp->plist); +#endif + btfreenode(nhead); + return 0; +} + +static int +parseplist(struct bttracker *tp, struct btnode *node) +{ + struct btnode *np; + struct btpeer *p; + char *host; + int port, n; + + switch (node->type) { + case NODE_LIST: + /* if the tracker answers with the peer list + * remove the compact flag for further requests */ + tp->compact = COMP_NO; + + for (; node != NULL; node = node->next) { + if ((p = calloc(1, sizeof(struct btpeer))) == NULL) + return -1; + + host = NULL; + port = 0; + + if ((np = btfinddict(node->list, "peer id")) != NULL) + memcpy(&p->peerid, np->str, SHA1LEN); + if ((np = btfinddict(node->list, "ip")) != NULL) + host = np->str; + if ((np = btfinddict(node->list, "port")) != NULL) + port = np->num; + + if (host != NULL) { + fillsin(host, port, &p->sin); + if (btaddpeer(&tp->plist, p)) + free(p); + } + } + break; + case NODE_STR: /* compact mode */ + tp->compact = COMP_YES; /* just to be sure */ + n = node->len / 6; + + while (n-- != 0) { + if ((p = calloc(1, sizeof(struct btpeer))) == NULL) + return -1; + + p->sin.sin_family = AF_INET; + memcpy(&p->sin.sin_addr, node->str + 6 * n, 4); + memcpy(&p->sin.sin_port, node->str + 6 * n + 4, 2); + + if (btaddpeer(&tp->plist, p) == 1) + free(p); + } + break; + default: + return -1; + } + + return 0; +} + +static const char *event[] = { NULL, "started", "completed", "stopped" }; + +static char * +buildrequest(struct bttracker *tp) +{ + char hash[HASHSZL]; + char *buf; + int len; + size_t buflen = 1024; + + if ((buf = calloc(buflen, sizeof(char))) == NULL) + return NULL; + + bthexdump(hash, tp->infohash, sizeof(hash)); + len = snprintf(buf, buflen, "GET /%s?info_hash=%s", tp->announce->path, hash); + + bthexdump(hash, tp->localid, sizeof(hash)); + len += snprintf(buf + len, buflen - len, "&peer_id=%s", hash); + + len += snprintf(buf + len, buflen - len, "&port=%d", tp->localport); + + if (tp->key != NULL) + len += snprintf(buf + len, buflen - len, "key=%s", tp->key); + + if (*tp->trackerid != NULL) { + bthexdump(hash, tp->trackerid, sizeof(hash)); + len += snprintf(buf + len, buflen - len, "trackerid=%s", hash); + } + + len += snprintf(buf + len, buflen - len, + "&uploaded=%lld&downloaded=%lld&left=%lld", + tp->uploaded, tp->downloaded, tp->left); + + if (tp->compact != COMP_NO) + len += snprintf(buf + len, buflen - len, + "&compact=%d", tp->compact); + + if (tp->numwait != 0) + len += snprintf(buf + len, buflen - len, + "&numwait=%d", tp->numwait); + + if (tp->event != EV_NONE) + len += snprintf(buf + len, buflen - len, + "&event=%s", event[tp->event]); + + len += snprintf(buf + len, buflen - len, + " HTTP/1.0\nHost: %s\nUser-Agent: %s/%d.%d\n\n", + tp->announce->host, USERAGENT, VERSION_MAJOR, VERSION_MINOR); + + return buf; +} + +/* TODO: review and add proper error handling + * as well as move it all into main select loop + */ +int +btcalltracker(struct bttracker *tp) +{ + char *buf; + size_t buflen, offset; + int sockd, ret; + struct timeval tv; + + if (gettimeofday(&tv, NULL) == -1) + err(1, "gettimeofday"); + tp->lastrqst = tv.tv_sec; /* set time mark */ + + if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + if (connect(sockd, (struct sockaddr *)&tp->sin, + sizeof(struct sockaddr)) == -1) + err(1, "connect"); + + if ((buf = buildrequest(tp)) == NULL) + return -1; +#if DEBUGTRACKER + printf("BUF (%d): %s\n", strlen(buf), buf); +#endif + if (send(sockd, buf, strlen(buf), 0) == -1) + err(1, "send"); + free(buf); + + buflen = sysconf(_SC_PAGESIZE); /* XXX */ + if ((buf = calloc(buflen, sizeof(char))) == NULL) + err(1, "alloc"); + + offset = 0; + for(;;) { + if (offset >= buflen) { + char *nbuf; + buflen *= 2; + if ((nbuf = realloc(buf, buflen)) == NULL) + err(1, "realloc"); + buf = nbuf; + } + if ((ret = recv(sockd, buf + offset, buflen - offset, 0)) == -1) + err(1, "recv"); + if (ret == 0) + break; + offset += ret; + } + close(sockd); +#if DEBUGTRACKER + /* remove all '\0' chars */ + if (strlen(buf) != 0) { + while ((strlen(buf)) < buflen) + buf[strlen(buf)] = ' '; + buf[buflen] = '\0'; + } + printf("BUF (%d): %s\n", buflen, buf); +#endif + + ret = parseanswer(tp, buf); + free(buf); + + return ret; +} + +struct bttracker * +btinittracker(struct btmeta *mp, int port) +{ + struct bttracker *tp; + + if ((tp = calloc(1, sizeof(struct bttracker))) == NULL) + return NULL; + + if (fillsin(mp->announce->host, mp->announce->port, &tp->sin) == -1) + return NULL; + + tp->announce = mp->announce; + tp->infohash = mp->infohash; + btpeerid(tp->localid); + + if ((tp->listener = startlistener(&port)) == -1) + err(1, "listen"); + tp->localport = port; + + tp->compact = COMP_YES; /* we start always in compact mode */ + *tp->trackerid = '\0'; /* isn't known at begin */ + tp->downloaded = 0; + tp->uploaded = 0; + tp->left = mp->left; + + tp->event = EV_STARTED; /* init value */ + + LIST_INIT(&tp->plist); + + return tp; +} + +/* TODO: if at all it should be called from `statistics calulator' */ +void +btupdatetracker(struct bttracker *tp, int dl, int ul, enum event e) +{ + tp->downloaded += dl; + tp->uploaded += ul; + tp->event = e; + /* XXX tp->left = mp->left; */ +} + +void +btfreetracker(struct bttracker *tp) +{ + btdelplist(&tp->plist); + free(tp); +} + +static int +fillsin(char *host, int port, struct sockaddr_in *sin) +{ + struct hostent *h; + + memset(sin, 0, sizeof(struct sockaddr_in)); + sin->sin_family = AF_INET; + if (inet_aton(host, &sin->sin_addr) == 0) { + h = gethostbyname(host); + if (h == NULL) { + err(1, "gethostbyname"); + return -1; + } + memcpy(&sin->sin_addr, h->h_addr, h->h_length); + } + sin->sin_port = htons(port); + + return 0; +} + +int +startlistener(int *port) +{ + struct sockaddr_in sin; + int maxport, yes = 1; + int sockd; + + maxport = *port + 8; + if (maxport > 65535) + maxport = 65535; + + if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return -1; + + if (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) + return -1; + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + memset(&sin.sin_zero, '\0', 8); + + for (; *port <= maxport; *port++) { + sin.sin_port = htons(*port); + if (bind(sockd, (struct sockaddr *)&sin, sizeof(sin)) == 0) + break; + } + + if (*port > maxport || listen(sockd, SOMAXCONN) == -1) { + close(sockd); + return -1; + } + + return sockd; +} -- cgit v1.2.3