summaryrefslogtreecommitdiff
path: root/tracker.c
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2008-11-13 16:17:55 +0000
committerDimitri Sokolyuk <demon@dim13.org>2008-11-13 16:17:55 +0000
commit51b913eb2ff91adfe8cd5e417759a6057c5a8a21 (patch)
tree95c550a91c9111de2f65b3c86ee0c81fef8f5398 /tracker.c
XBitTorrent
Diffstat (limited to 'tracker.c')
-rw-r--r--tracker.c398
1 files changed, 398 insertions, 0 deletions
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 <demon@vhost.dyndns.org>
+ *
+ * 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 <sys/types.h> /* htons() */
+#include <sys/time.h>
+#include <sys/socket.h> /* AF_INET */
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <arpa/inet.h> /* inet_ntoa() */
+#include <netdb.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h> /* 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;
+}