From 51b913eb2ff91adfe8cd5e417759a6057c5a8a21 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Thu, 13 Nov 2008 16:17:55 +0000 Subject: XBitTorrent --- Makefile | 16 ++ Makefile.inc | 28 ++++ bencode.c | 285 ++++++++++++++++++++++++++++++++ bencode.h | 56 +++++++ btcheck.c | 127 ++++++++++++++ btcheck/Makefile | 7 + btget.c | 114 +++++++++++++ btget/Makefile | 7 + btshow.c | 104 ++++++++++++ btshow/Makefile | 7 + clients.c | 69 ++++++++ context.c | 59 +++++++ context.h | 53 ++++++ files.c | 271 ++++++++++++++++++++++++++++++ files.h | 42 +++++ lib/Makefile | 16 ++ lib/shlib_version | 2 + meta.c | 265 +++++++++++++++++++++++++++++ meta.h | 91 ++++++++++ netio.c | 185 +++++++++++++++++++++ netio.h | 28 ++++ peer.c | 487 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ peer.h | 130 +++++++++++++++ tools.c | 230 ++++++++++++++++++++++++++ tools.h | 40 +++++ tracker.c | 398 ++++++++++++++++++++++++++++++++++++++++++++ tracker.h | 74 +++++++++ version.h | 9 + 28 files changed, 3200 insertions(+) create mode 100644 Makefile create mode 100644 Makefile.inc create mode 100644 bencode.c create mode 100644 bencode.h create mode 100644 btcheck.c create mode 100644 btcheck/Makefile create mode 100644 btget.c create mode 100644 btget/Makefile create mode 100644 btshow.c create mode 100644 btshow/Makefile create mode 100644 clients.c create mode 100644 context.c create mode 100644 context.h create mode 100644 files.c create mode 100644 files.h create mode 100644 lib/Makefile create mode 100644 lib/shlib_version create mode 100644 meta.c create mode 100644 meta.h create mode 100644 netio.c create mode 100644 netio.h create mode 100644 peer.c create mode 100644 peer.h create mode 100644 tools.c create mode 100644 tools.h create mode 100644 tracker.c create mode 100644 tracker.h create mode 100644 version.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..74e8024 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +# $Id$ + +.include + +SUBDIR= lib btshow btcheck btget # xbittorrent + +tags: + ctags -dt *.[ch] + +cs: tags + ls -1 *.[ch] > cscope.files + cscope-indexer -v + +.PHONY: tags cs + +.include diff --git a/Makefile.inc b/Makefile.inc new file mode 100644 index 0000000..ae40db6 --- /dev/null +++ b/Makefile.inc @@ -0,0 +1,28 @@ +# $Id$ + +.PATH: ${.CURDIR}/.. +CFLAGS+= -I${.CURDIR}/.. + +CFLAGS+= -DMMAP -DFAILURE +#DEBUG= -DDEBUG +#DEBUG= -DDEBUGTRACKER +WARNINGS= yes + +CDIAGFLAGS+= -Wall -Wpointer-arith \ + -Wstrict-prototypes -Wmissing-prototypes \ + -ggdb +LDFLAGS+= -pg +CFLAGS+= -pg +#CDIAGFLAGS+= -Wconversion -ansi -pedantic + +.include + +.if exists(${.CURDIR}/../lib/${__objdir}) +LDADD+= -L${.CURDIR}/../lib/${__objdir} -lxbt +DPADD+= ${.CURDIR}/../lib/${__objdir}/libxbt.a +.else +LDADD+= -L${.CURDIR}/../lib -lxbt +DPADD+= ${.CURDIR}/../lib/libxbt.a +.endif +LDADD+= -lcrypto +DPADD+= ${LIBCRYPTO} diff --git a/bencode.c b/bencode.c new file mode 100644 index 0000000..d03523b --- /dev/null +++ b/bencode.c @@ -0,0 +1,285 @@ +/* $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 +#include +#include +#include +#include + +#include "bencode.h" +#include "tools.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +static struct btnode *dispatch(char **); +static struct btnode *getdict(char **); +static struct btnode *getlist(char **); +static struct btnode *getint(char **); +static struct btnode *getstr(char **); +static off_t getnum(char **); +#if DEBUG +static char *indent(int); /* used only for debug purposes */ +#endif /* DEBUG */ + +struct btnode * +btbencode(char *str) +{ + return dispatch(&str); +} + +void +btfreenode(struct btnode *np) +{ + if (np == NULL) + return; + + if (np->next != NULL) + btfreenode(np->next); + + switch (np->type) { + case NODE_DICT: + btfreenode(np->dict.key); + btfreenode(np->dict.val); + break; + case NODE_LIST: + btfreenode(np->list); + break; + case NODE_INT: + /* nothing */ + break; + case NODE_STR: + free(np->str); + break; + } + free(np); +} + +struct btnode * +btfinddict(struct btnode *np, char *key) +{ + if (np == NULL || key == NULL) + return NULL; + + if (np->type != NODE_DICT) + return NULL; + + if (np->dict.key == NULL) + return NULL; + + /* the key is always string */ + if (np->dict.key->type != NODE_STR) + return NULL; + + if (np->dict.key->str == NULL) + return NULL; + + /* there is only one key entry per dict possible */ + if (strncmp(np->dict.key->str, key, + strlen(np->dict.key->str)) == 0) + return np->dict.val; + + return btfinddict(np->next, key); +} + +static struct btnode * +dispatch(char **c) +{ + switch (*(*c)++) { + case 'd': + return getdict(c); + case 'l': + return getlist(c); + case 'i': + return getint(c); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + --*c; + return getstr(c); + case 'e': + default: + return NULL; + } + /* NOTREACHED */ +} + +static struct btnode * +getdict(char **c) +{ + struct btnode *np; + char *off; + + if ((np = calloc(1, sizeof(struct btnode))) == NULL) + return NULL; + + np->type = NODE_DICT; + + if ((np->dict.key = dispatch(c)) == NULL) { + free(np); /* empty dictonary */ + return NULL; + } + + off = *c; + np->dict.val = dispatch(c); + if (np->dict.val != NULL) { + np->dict.val->aux.off = off; + np->dict.val->aux.len = *c - off; + } + + np->next = getdict(c); + + return np; +} + +static struct btnode * +getlist(char **c) +{ + struct btnode *np; + + if ((np = calloc(1, sizeof(struct btnode))) == NULL) + return NULL; + + np->type = NODE_LIST; + + if ((np->list = dispatch(c)) == NULL) { + free(np); /* empty list */ + return NULL; + } + + np->next = getlist(c); + + return np; +} + +static struct btnode * +getint(char **c) +{ + struct btnode *np; + + if ((np = calloc(1, sizeof(struct btnode))) == NULL) + return NULL; + + np->type = NODE_INT; + np->num = getnum(c); + + return np; +} + +static off_t +getnum(char **c) +{ + off_t n = 0; + int mflag = 0; + + if (**c == '-') { + mflag = 1; + ++*c; + } + + while (**c >= '0' && **c <= '9') { + n *= 10; + n += *(*c)++ - '0'; + } + ++*c; /* skip the last char (usually `e' or `:') */ + + return mflag ? -n : n; +} + +static struct btnode * +getstr(char **c) +{ + off_t len; + struct btnode *np; + + if ((len = getnum(c)) <= 0) + return NULL; + + if ((np = calloc(1, sizeof(struct btnode))) == NULL) + return NULL; + + np->type = NODE_STR; + np->len = len; + + if ((np->str = calloc(len + 1, sizeof(char))) == NULL) { + free(np); + return NULL; + } + + memcpy(np->str, *c, len); + np->str[len] = NULL; + *c += len; + + return np; +} + +#if DEBUG +static char * +indent(int n) +{ + int i = 0; + static char buf[80] = { NULL }; + + while (i < n && i < 79) + buf[i++] = ' '; + buf[i] = NULL; + + return buf; +} + +void +btdumpnode(struct btnode *np) +{ + const char *type[] = { "Dictonary", "List", "Integer", "String" }; + static int i = 0; + + if (np == NULL) + return; + printf("%s%s\n", indent(i), type[np->type]); + switch (np->type) { + case NODE_DICT: + i += 8; + btdumpnode(np->dict.key); + btdumpnode(np->dict.val); + i -= 8; + break; + case NODE_LIST: + i += 8; + btdumpnode(np->list); + i -= 8; + break; + case NODE_INT: + printf("%s%lld\n", indent(i + 4), np->num); + break; + case NODE_STR: + printf("%s(%d) %s\n", indent(i + 4), np->len, np->str); + break; + } + + if (np->next != NULL) + btdumpnode(np->next); +} +#endif /* DEBUG */ diff --git a/bencode.h b/bencode.h new file mode 100644 index 0000000..ca4dd91 --- /dev/null +++ b/bencode.h @@ -0,0 +1,56 @@ +/* $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. + */ + +#ifndef _BENCODE_H_ +#define _BENCODE_H_ + +enum node { + NODE_DICT, + NODE_LIST, + NODE_INT, + NODE_STR +}; + +struct btnode { + enum node type; + char *str; + int len; + off_t num; + struct btnode *list; + struct { + struct btnode *key; + struct btnode *val; + } dict; + struct btnode *next; + + /* auxiliary variables, needed only to calculate info_hash */ + struct { + char *off; /* offset of node in string */ + long len; /* entire length of node */ + } aux; +}; + +__BEGIN_DECLS +struct btnode *btbencode(char *); +void btfreenode(struct btnode *); +struct btnode *btfinddict(struct btnode *, char *); +#if DEBUG +void btdumpnode(struct btnode *); +#endif /* DEBUG */ +__END_DECLS + +#endif /* Not _BENCODE_H_ */ diff --git a/btcheck.c b/btcheck.c new file mode 100644 index 0000000..de62d72 --- /dev/null +++ b/btcheck.c @@ -0,0 +1,127 @@ +/* $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 + +#include +#include +#include +#include +#include + +#include "meta.h" +#include "files.h" +#include "tools.h" +#include "version.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +const char usagearg[] = "[-v] torrent [torrent ...]"; + +void progress(struct btmeta *, int); +void done(struct btmeta *); +void printbf(char *, int); + +void +printbf(char *bf, int sz) +{ + int i; + + for (i = 0; i < sz * NBBY; i++) { + printf("%c", "01"[!!btisset(bf, i)]); + if ((i + 1) % 8 == 0) + printf("%c", "\n "[!!((i + 1) % 0x40)]); + } + printf("\n"); +} + +int +main(int argc, char **argv) +{ + extern char *__progname; + struct btmeta *mp; + struct progresscb cb; + int ch, vflag; + + vflag = 0; + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + vflag = 1; + break; + case '?': + default: + usage(usagearg); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) + usage(usagearg); + + printf("%s %d.%d - check BitTorrent files\n", + __progname, VERSION_MAJOR, VERSION_MINOR); + + for (; *argv != NULL; ++argv) { + if ((mp = btreadmeta(*argv)) == NULL) { + warnx("cannot read %s\n", *argv); + continue; + } + + if (btmkhier(mp) == -1) + return -1; + printf("\nfile: %s\n", mp->fname); + printf("size: %s\n", metric((double) mp->size)); +#if 0 + btchkfiles(mp, progress); + done(mp); +#else + cb.progress = progress; + cb.done = done; + btchkfiles(mp, &cb); +#endif + if (vflag) + printbf(mp->bitfield, mp->bitlen); + + btfreemeta(mp); + } + return 0; +} + +void +progress(struct btmeta *mp, int n) +{ + if (n % 10 == 0) { + printf("\rchecking %d of %d (%d ok)", + n, mp->npieces, mp->good); + fflush(stdout); + } +} + +void +done(struct btmeta *mp) +{ + printf("\rtotal good pieces %d/%d (%d%%)\n", + mp->good, mp->npieces, mp->good * 100 / mp->npieces); + + printf("left: %s\n", metric((double) mp->left)); +} diff --git a/btcheck/Makefile b/btcheck/Makefile new file mode 100644 index 0000000..2f9f2f8 --- /dev/null +++ b/btcheck/Makefile @@ -0,0 +1,7 @@ +# $Id$ + +PROG= btcheck +SRCS= btcheck.c +NOMAN= yes + +.include diff --git a/btget.c b/btget.c new file mode 100644 index 0000000..03cb37f --- /dev/null +++ b/btget.c @@ -0,0 +1,114 @@ +/* $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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include /* close(), getopt() */ + +#include "bencode.h" +#include "context.h" +#include "meta.h" +#include "files.h" +#include "peer.h" +#include "tools.h" +#include "tracker.h" +#include "version.h" + +const char usagearg[] = "torrent"; + +int +main(int argc, char **argv) +{ + struct btmeta *mp; + struct bttracker *tp; + struct progresscb cb; + enum event ev = EV_NONE; + int ch, port = DEFPORT + 1; + + while ((ch = getopt(argc, argv, "p:skn")) != -1) + switch (ch) { + case 'p': + port = atoi(optarg); + if (port > 65535 || port < 1024) + usage(usagearg); + /* NOTREACHED */ + break; + case 's': + ev = EV_STARTED; + break; + case 'k': + ev = EV_STOPPED; + break; + case 'n': + ev = EV_NONE; + break; + case '?': + default: + usage(usagearg); + /* NOTREACHED */ + } + + argc -= optind; + argv += optind; + + if (argc != 1 || (mp = btreadmeta(*argv)) == NULL) + usage(usagearg); + + if (btmkhier(mp) == -1) + return -1; + cb.progress = btchkprogress; + cb.done = btchkdone; + btchkfiles(mp, &cb); + + tp = btinittracker(mp, port); + + /* ---8<------ */ + memcpy(tp->localid, "-XB0100-AAAAAAAAAAAA", SHA1LEN); + tp->event = ev; + + /* DEBUG: initial call */ + tp->event = EV_STARTED; + btcalltracker(tp); + btdumpplist(&tp->plist); + printf("def interval: %d\n", tp->interval); + printf("min interval: %d\n", tp->minterval); + + sleep(10); + + tp->event = EV_NONE; + btcalltracker(tp); + btdumpplist(&tp->plist); + + /* DEBUG: final call */ + tp->event = EV_STOPPED; + btcalltracker(tp); + + /* btmainloop(); */ + + btfreetracker(tp); + + btfreemeta(mp); + return 0; +} diff --git a/btget/Makefile b/btget/Makefile new file mode 100644 index 0000000..4e20220 --- /dev/null +++ b/btget/Makefile @@ -0,0 +1,7 @@ +# $Id$ + +PROG= btget +SRCS= btget.c +NOMAN= yes + +.include diff --git a/btshow.c b/btshow.c new file mode 100644 index 0000000..3cf5c88 --- /dev/null +++ b/btshow.c @@ -0,0 +1,104 @@ +/* $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 +#include +#include + +#include +#include +#include +#include /* basename(3) */ +#include "meta.h" +#include "tools.h" +#include "version.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +const char usagearg[] = "torrent [torrent ...]"; + +extern char *__progname; + +int +main(int argc, char **argv) +{ + struct btmeta *mp; + struct btfile *fp; + char hash[HASHSZS]; + + if (argc < 2) + usage(usagearg); + + printf("%s %d.%d - decode BitTorrent metainfo files\n\n", + __progname, VERSION_MAJOR, VERSION_MINOR); + + while(*++argv != NULL) { + if ((mp = btreadmeta(*argv)) == NULL) { + warnx("cannot read %s\n", *argv); + continue; + } + + printf("metainfo file.: %s\n", basename(mp->fname)); + bthexdump(hash, mp->infohash, sizeof(hash)); + printf("info hash.....: %s\n", hash); + + if (mp->nfiles == 1) { + printf("file name.....: %s\n", mp->name); + } else { + printf("directory name: %s\n", mp->name); + printf("files.........: %d\n", mp->nfiles); + + SIMPLEQ_FOREACH(fp, &mp->flist, link) { + printf(" %s (%lld) %s\n", + fp->path, + fp->length, + metric((double) fp->length)); + } + } + + printf("%s %lld (%lld * %lld + %lld) %s\n", + mp->nfiles == 1 ? + "file size.....:" : + "archive size..:", + mp->size, mp->size / mp->plength, + mp->plength, mp->size % mp->plength, + metric((double) mp->size)); + + printf("announce url..: http://%s", mp->announce->host); + if (mp->announce->port != 80) + printf(":%d", mp->announce->port); + putchar('/'); + if (mp->announce->path != NULL) + printf("%s", mp->announce->path); + putchar('\n'); + + /* optional fields */ + if (mp->date != 0) + printf("creation date.: %s", ctime(&mp->date)); + if (mp->comment != NULL) + printf("comment.......: %s\n", mp->comment); + if (mp->by != NULL) + printf("created by....: %s\n", mp->by); + + btfreemeta(mp); + printf("\n"); + } + + return 0; +} diff --git a/btshow/Makefile b/btshow/Makefile new file mode 100644 index 0000000..24a52f8 --- /dev/null +++ b/btshow/Makefile @@ -0,0 +1,7 @@ +# $Id$ + +PROG= btshow +SRCS= btshow.c +NOMAN= yes + +.include diff --git a/clients.c b/clients.c new file mode 100644 index 0000000..de3aa73 --- /dev/null +++ b/clients.c @@ -0,0 +1,69 @@ +struct client { + char *id; + char *name; +}; + +/* Azureus-style uses the following encoding: + '-', two characters for client id, + four ascii digits for version number, + '-', + followed by random numbers. + + For example: '-AZ2060-'... + */ + +struct client azureus[] = { + { "AR", "Arctic" }, + { "AX", "BitPump" }, + { "AZ", "Azureus" }, + { "BB", "BitBuddy" }, + { "BC", "BitComet" }, + { "BS", "BTSlave" }, + { "BX", "Bittorrent X" }, + { "CD", "Enahnced CTorrent" }, + { "CT", "CTorrent" }, + { "LP", "Lphant" }, + { "LT", "libtorrent" }, + { "lt", "libTorrent" }, + { "MP", "MooPolice" }, + { "MT", "MoonlightTorrent" }, + { "QT", "Qt 4 Torrent example" }, + { "RT", "Retriever" }, + { "SB", "Swiftbit" }, + { "SS", "SwarmScope" }, + { "SZ", "Shareaza" }, + { "TN", "TorrentDotNET" }, + { "TR", "Transmission" }, + { "TS", "Torrentstorm" }, + { "UT", "uTorrent" }, /* µTorrent */ + { "XT", "XanTorrent" }, + { "XB", "XBitTorrent" }, + { "ZT", "ZipTorrent" }, + { NULL, NULL } +}; + +/* Shadow's style uses the following encoding: + one ascii alphanumeric for client identification, + three ascii digits for version number, + '----', + followed by random numbers. + + For example: 'S587----'... + */ + +struct client shadow[] = { + { "A", "ABC" }, + { "O", "Osprey Permaseed" }, + { "R", "Tribler" }, + { "S", "Shadow's client" }, + { "T", "BitTornado" }, + { "U", "UPnP NAT Bit Torrent" }, + { NULL, NULL } +}; + +/* Bram's client now uses this style... 'M3-4-2--' */ + +struct client bram[] = { + { "M", "BitTorrent" }, + { NULL, NULL } +}; diff --git a/context.c b/context.c new file mode 100644 index 0000000..8b2fded --- /dev/null +++ b/context.c @@ -0,0 +1,59 @@ +/* $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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "context.h" +#include "meta.h" +#include "peer.h" +#include "tracker.h" +#include "tools.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +void +btmainloop(int listener) +{ + struct timeval tv; + fd_set readfds; + int fdmax; + + tv.tv_sec = 60; + tv.tv_usec = 0; + + FD_ZERO(&readfds); + FD_SET(listener, &readfds); + fdmax = listener; + + for (;;) { + if (select(fdmax + 1, &readfds, NULL, NULL, &tv) == -1) + err(1, "select"); + } +} diff --git a/context.h b/context.h new file mode 100644 index 0000000..7d15ac8 --- /dev/null +++ b/context.h @@ -0,0 +1,53 @@ +/* $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. + */ + +#ifndef _CONTEXT_H_ +#define _CONTEXT_H_ + +#define DEFPORT 6881 +#define MINPORT 6881 +#define MAXPORT 6889 + +struct btsock { + int sockd; + struct sockaddr_in sin; + struct btsock *next; /* isn't used yet */ +}; + +struct btcontext { + struct btsock listen; + struct btsock *peers; +}; + +#if 0 +LIST_HEAD(btplist, btpeer); +SIMPLEQ_HEAD(btflist, btfile); /* ??? */ + +struct btorrent { + struct btannonce *annonce; /* tracker */ + struct btsock *server; /* local server */ + struct btplist *plist; /* peer list */ + struct btmeta *meta; /* meta file */ + struct btfile *flist; /* file list */ +}; +#endif + +__BEGIN_DECLS +void btmainloop(int); +__END_DECLS + +#endif /* not _CONTEXT_H_ */ diff --git a/files.c b/files.c new file mode 100644 index 0000000..4fb6d49 --- /dev/null +++ b/files.c @@ -0,0 +1,271 @@ +/* $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 + +#include /* bitfield */ +#include +#include /* mkdir() */ +#include + +#include + +#include +#include +#include + +#include "meta.h" +#include "files.h" +#include "tools.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +/* + * 0 1 2 3 4 5 6 7 + * pieces +---+---+---+---+---+---+---+-+ + * files +--------+-------+-+----------+ + * 0 1 2 3 + */ + +/* TODO: rewrite btread() and btwrite() so, that + * only required files are open (pending, DONE) + * on the one hand it makes more overhead on opening files, on the other + * hand we need only one file descriptor and we have no limitations on number of files + * so more descriptors remains for the network communication by the same rlimit value + */ + +#define FMODE "a+" + +int +btwrite(struct btmeta *mp, char *piece, off_t len, int npiece) +{ + FILE *fd; + struct btfile *fp; + off_t off, eof, left; + + fp = SIMPLEQ_FIRST(&mp->flist); + /* TODO: check piece first */ + off = mp->plength * npiece; + eof = fp->length; + + /* find right file */ + while (off > eof) { + off -= fp->length; + fp = SIMPLEQ_NEXT(fp, link); + eof = fp->length; + } + if (fp == NULL) + return -1; /* XXX */ + + left = len; + fd = fopen(fp->path, FMODE); + fseek(fd, off, SEEK_SET); + if ((off + len) <= eof) { + left -= fwrite(piece, sizeof(char), len, fd); + } else { + left -= fwrite(piece, sizeof(char), eof - off, fd); + fclose(fd); + fp = SIMPLEQ_NEXT(fp, link); + + for (; left > fp->length; fp = SIMPLEQ_NEXT(fp, link)) { + fd = fopen(fp->path, FMODE); + fseek(fd, 0L, SEEK_SET); + left -= fwrite(piece + len - left, sizeof(char), + fp->length, fd); + fclose(fd); + } + + fd = fopen(fp->path, FMODE); + fseek(fd, 0L, SEEK_SET); + left -= fwrite(piece + len - left, sizeof(char), left, fd); + } + fclose(fd); + + return ((len - left) == 0); +} + +int +btread(struct btmeta *mp, char *piece, off_t len, int npiece) +{ + FILE *fd; + struct btfile *fp; + off_t off, eof, left, ret; + + fp = SIMPLEQ_FIRST(&mp->flist); + off = mp->plength * npiece; + eof = fp->length; + + /* find right file */ + while (off > eof) { + off -= fp->length; + fp = SIMPLEQ_NEXT(fp, link); + eof = fp->length; + } + + left = len; + fd = fopen(fp->path, FMODE); + fseek(fd, off, SEEK_SET); + if ((off + len) <= eof) { + ret = fread(piece, sizeof(char), len, fd); + left -= (ret > 0) ? ret : len; + } else { + ret = fread(piece, sizeof(char), eof - off, fd); + left -= (ret > 0) ? ret : eof - off; + fclose(fd); + fp = SIMPLEQ_NEXT(fp, link); + + for (; left > fp->length; fp = SIMPLEQ_NEXT(fp, link)) { + fd = fopen(fp->path, FMODE); + fseek(fd, 0L, SEEK_SET); + ret = fread(piece + len - left, sizeof(char), + fp->length, fd); + left -= (ret > 0) ? ret : fp->length; + fclose(fd); + } + + fd = fopen(fp->path, FMODE); + fseek(fd, 0L, SEEK_SET); + ret = fread(piece + len - left, sizeof(char), left, fd); + left -= (ret > 0) ? ret : left; + } + fclose(fd); + + return (ret > 0); +} + +off_t +btsizeofpiece(struct btmeta *mp, int n) +{ + return (n == mp->npieces - 1) ? mp->size % mp->plength : mp->plength; +} + +int +btmkhier(struct btmeta *mp) +{ + struct btfile *fp; + struct stat st; + char path[MAXPATHLEN], *p; + + if (mp->nfiles > 1) { + SIMPLEQ_FOREACH(fp, &mp->flist, link) { + strlcpy(path, fp->path, sizeof(path)); + p = path; + while ((p = strchr(p, '/')) != NULL) { + /* TODO: add proper checking */ + *p = '\0'; + if (stat(path, &st) == -1) + mkdir(path, 0755); + else if (!(st.st_mode & S_IFDIR)) { + fprintf(stderr, "remove %s\n", path); + return -1; + } + *p++ = '/'; + } + } + } + + return 0; +} + +int +btcheck(u_char *key, char *src, off_t len) +{ + u_char sha[SHA1LEN]; + + /* FIXME: it's so ugly */ + SHA1((u_char *) src, len, sha); + return (strncmp((char *) key, (char *) sha, SHA1LEN) == 0); +} + +void +#if 0 +btchkfiles(struct btmeta *mp, void (*progress)(struct btmeta *, int)) +#else +btchkfiles(struct btmeta *mp, struct progresscb *cb) +#endif +{ + char *buf; + off_t len; + int n; + + if ((buf = calloc(mp->plength, sizeof(char))) == NULL) + return; + + mp->left = mp->size; + + for (n = 0, mp->good = 0; n < mp->npieces; n++) { + len = btsizeofpiece(mp, n); + if (btread(mp, buf, len, n)) { + if (btcheck(mp->pieces[n], buf, len)) { + btsetbit(mp->bitfield, n); + mp->good++; + mp->left -= len; + } + } +#if 0 + if (progress != NULL) + (*progress)(mp, n + 1); + } + if (progress != NULL) + (*progress)(mp, n + 1); +#else + if (cb && cb->progress) + (*cb->progress)(mp, n + 1); + } + if (cb && cb->done) + (*cb->done)(mp); +#endif + + free(buf); +} + +/* XXX */ +int +btchkwrite(struct btmeta *mp, char *buf, int n) +{ + off_t len; + + len = btsizeofpiece(mp, n); + + if (btcheck(mp->pieces[n], buf, len)) { + if (btwrite(mp, buf, len, n)) { + btsetbit(mp->bitfield, n); + mp->left -= len; + return 0; + } + } + return -1; +} + +/* fallback progress indicator */ + +void +btchkprogress(struct btmeta *mp, int n) +{ + printf("\r%s: checking %d of %d (%d ok)", mp->fname, + n, mp->npieces, mp->good); + fflush(stdout); +} + +void +btchkdone(struct btmeta *mp) +{ + printf("\r%s: total good pieces %d/%d (%d%%)\n", mp->fname, + mp->good, mp->npieces, mp->good * 100 / mp->npieces); +} diff --git a/files.h b/files.h new file mode 100644 index 0000000..19898de --- /dev/null +++ b/files.h @@ -0,0 +1,42 @@ +/* $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. + */ + +#ifndef _FILES_H_ +#define _FILES_H_ + +struct progresscb { + void (*progress)(struct btmeta *, int); + void (*done)(struct btmeta *); +}; + +__BEGIN_DECLS +off_t btsizeofpiece(struct btmeta *, int); +int btcheck(u_char *, char *, off_t); +int btwrite(struct btmeta *, char *, off_t, int); +int btread(struct btmeta *, char *, off_t, int); +int btmkhier(struct btmeta *); +#if 0 +void btchkfiles(struct btmeta *, void(*)(struct btmeta *, int)); +#else +void btchkfiles(struct btmeta *, struct progresscb *); +#endif +int btchkwrite(struct btmeta *, char *, int); +void btchkprogress(struct btmeta *, int); +void btchkdone(struct btmeta *); +__END_DECLS + +#endif /* not _FILES_H_ */ diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..8fbfd71 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,16 @@ +# $Id$ + +LIB= xbt +SRCS= bencode.c \ + context.c \ + files.c \ + meta.c \ + peer.c \ + tools.c \ + tracker.c +# netio.c +NOMAN= yes +NOPROFILE= yes +NOPIC= yes + +.include diff --git a/lib/shlib_version b/lib/shlib_version new file mode 100644 index 0000000..1edea46 --- /dev/null +++ b/lib/shlib_version @@ -0,0 +1,2 @@ +major=1 +minor=0 diff --git a/meta.c b/meta.c new file mode 100644 index 0000000..6255e2e --- /dev/null +++ b/meta.c @@ -0,0 +1,265 @@ +/* $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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "bencode.h" +#include "meta.h" +#include "files.h" +#include "tools.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +static char *encpath(struct btnode *, char *); +static struct btmeta *parsemeta(struct btnode *); +static struct btannounce *getannounce(char *); +static void freeannounce(struct btannounce *); + +struct btmeta * +btreadmeta(char *fname) +{ + /* TODO: realloc btmeta, if the next torrent is to read */ + struct btnode *np; + struct btmeta *mp; + struct stat st; + char *buf; + + if (stat(fname, &st) == -1 || !(st.st_mode & S_IFREG)) + return NULL; + +#if MMAP + off_t len; + if ((buf = (char *) btmmapfile(fname, &len)) == NULL) +#else + if ((buf = btfreadall(fname)) == NULL) +#endif + return NULL; + + /* the first node must be a dictionary followed by a digit */ + if (*buf != 'd' || !(isascii(*(buf + 1)) && isdigit(*(buf + 1)))) + return NULL; + + np = btbencode(buf); + if ((mp = parsemeta(np)) == NULL) { + fprintf(stderr, "parsemeta\n"); + return NULL; + } + +#if DEBUG + btdumpnode(np); +#endif + + mp->fname = strdup(fname); + +#if MMAP + btunmapfile((void *) buf, len); +#else + free(buf); +#endif + btfreenode(np); + + return mp; +} + +void +btfreemeta(struct btmeta *mp) +{ + struct btfile *fp, *nxt; + + if (mp == NULL) + return; + + freeannounce(mp->announce); + free(mp->name); + free(mp->fname); + for (fp = SIMPLEQ_FIRST(&mp->flist); fp != SIMPLEQ_END(&mp->flist); fp = nxt) { + nxt = SIMPLEQ_NEXT(fp, link); + free(fp->path); + free(fp); + } + SIMPLEQ_INIT(&mp->flist); + free(mp->pieces); + if (mp->comment != NULL) + free(mp->comment); + if (mp->by != NULL) + free(mp->by); + free(mp->bitfield); + free(mp); +} + +static char * +encpath(struct btnode *np, char *parent) +{ + char buf[MAXPATHLEN]; + + if (np->list == NULL) + return NULL; + + strlcpy(buf, parent, sizeof(buf)); + strlcat(buf, "/", sizeof(buf)); + while (np != NULL) { + strlcat(buf, np->list->str, sizeof(buf)); + strlcat(buf, "/", sizeof(buf)); + np = np->next; + } + buf[strlen(buf) - 1] = '\0'; + + return strdup(buf); +} + +/* FIXME: should it all be there or in main? */ +static struct btmeta * +parsemeta(struct btnode *np) +{ + struct btnode *found, *info, *files; + struct btmeta *mp; + struct btfile *fp; + + if ((mp = calloc(1, sizeof(struct btmeta))) == NULL) + return NULL; + + if ((found = btfinddict(np, "announce")) != NULL) + if ((mp->announce = getannounce(found->str)) == NULL) + return NULL; + + /* TODO: announce-list */ + + if ((found = btfinddict(np, "comment")) != NULL) + mp->comment = strdup(found->str); + + if ((found = btfinddict(np, "creation date")) != NULL) + mp->date = found->num; + + if ((found = btfinddict(np, "created by")) != NULL) + mp->by = strdup(found->str); + + if ((info = btfinddict(np, "info")) == NULL) + return NULL; + + SHA1(info->aux.off, info->aux.len, mp->infohash); + + if ((found = btfinddict(info, "name")) != NULL) + mp->name = strdup(found->str); + + SIMPLEQ_INIT(&mp->flist); + + if ((found = btfinddict(info, "length")) != NULL) { + if ((fp = calloc(1, sizeof(struct btfile))) == NULL) + return NULL; + + fp->length = mp->size = found->num; + fp->path = strdup(mp->name); + + SIMPLEQ_INSERT_TAIL(&mp->flist, fp, link); + mp->nfiles = 1; + } else { + mp->size = 0; + mp->nfiles = 0; + files = btfinddict(info, "files"); + while (files != NULL) { + if ((fp = calloc(1, sizeof(struct btfile))) == NULL) + return NULL; + if ((found = btfinddict(files->list, "length")) != NULL) { + fp->length = found->num; + mp->size += found->num; + } + + /* TODO: change encpath so that it returns (char **) */ + if ((found = btfinddict(files->list, "path")) != NULL) + fp->path = encpath(found, mp->name); + + SIMPLEQ_INSERT_TAIL(&mp->flist, fp, link); + ++mp->nfiles; + + files = files->next; + } + } + + if ((found = btfinddict(info, "piece length")) != NULL) + mp->plength = found->num; + + if ((found = btfinddict(info, "pieces")) != NULL) { + int i; + mp->npieces = howmany(mp->size, mp->plength); + if ((mp->pieces = calloc(mp->npieces, sizeof(*mp->pieces))) == NULL) + return NULL; + for (i = 0; i < mp->npieces; i++) + memcpy(mp->pieces[i], &found->str[i * SHA1LEN], SHA1LEN); + } + + /* initialize bitfield */ + mp->bitlen = howmany(mp->npieces, sizeof(char) * NBBY); + if ((mp->bitfield = calloc(mp->bitlen, sizeof(char))) == NULL) + return NULL; + + return mp; +} + +static struct btannounce * +getannounce(char *src) +{ +#define HTTP_URL "http://" +#define HTTP_PORT 80 + + struct btannounce *ap; + char *buf, *host, *port, *path; + + buf = strdup(src); + + if ((ap = calloc(1, sizeof(struct btannounce))) == NULL) + return NULL; + + if ((host = strcasestr(buf, HTTP_URL)) == NULL) { + free(buf); + return NULL; + } else + host += sizeof(HTTP_URL) - 1; + + if ((path = strchr(host, '/')) != NULL) + *path++ = '\0'; + ap->path = path ? strdup(path) : NULL; + + if ((port = strrchr(host, ':')) != NULL) + *port++ = '\0'; + ap->port = port ? atoi(port) : HTTP_PORT; + + ap->host = strdup(host); /* have to be last */ + + free(buf); + + return ap; +} + +static void +freeannounce(struct btannounce *ap) +{ + if (ap->path) + free(ap->path); /* XXX */ + free(ap->host); + free(ap); +} diff --git a/meta.h b/meta.h new file mode 100644 index 0000000..c6ba67c --- /dev/null +++ b/meta.h @@ -0,0 +1,91 @@ +/* $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. + */ +#ifndef _META_H_ +#define _META_H_ + +#if 0 +#include +#endif + +#ifndef SHA1LEN +#define SHA1LEN 20 +#endif + +#define btsetbit(a, i) ((a)[(i) / NBBY] |= 1 << (NBBY - ((i) % NBBY) - 1)) +#define btclrbit(a, i) ((a)[(i) / NBBY] &= ~(1 << (NBBY - ((i) % NBBY) - 1))) +#define btisset(a, i) ((a)[(i) / NBBY] & (1 << (NBBY - ((i) % NBBY) - 1))) +#define btisclr(a, i) (((a)[(i) / NBBY] & (1 << (NBBY - ((i) % NBBY) - 1))) == 0) + +struct btannounce { + SIMPLEQ_ENTRY(btannounce) link; /* isn't used jet, preparation for an ann-list */ +#if 0 /* obsolete */ + struct sockaddr_in sin; +#endif + char *host; + int port; + char *path; +}; + +SIMPLEQ_HEAD(btalist, btannounce); + +struct btfile { + SIMPLEQ_ENTRY(btfile) link; + off_t length; + char *path; +}; + +SIMPLEQ_HEAD(btflist, btfile); + +struct btmeta { + char *fname; + + int nann; /* number of tracker */ + struct btalist btalist; /* SIMPLEQ_HEAD */ + + struct btannounce *announce; /* announce url */ + char *name; /* name of file or directory */ + off_t plength; /* length of one piece */ + int npieces; /* number of pieces */ + u_char (*pieces)[SHA1LEN]; /* pieces SHA1 check-sum */ + u_char infohash[SHA1LEN]; /* needed by peer */ + off_t size; /* size overall */ + int nfiles; /* num of files */ + struct btflist flist; /* SIMPLEQ_HEAD */ + + /* aux */ + char *comment; + char *by; /* publisher */ + time_t date; /* published */ + + /* should that all be there? */ + int bitlen; + char *bitfield; /* needed by peer (bitfield) */ + int good; /* good pieces */ + + off_t left; /* needed by tracker */ +#if 0 + struct btmeta *next; /* XXX next torrent, isn't used yet */ +#endif +}; + +__BEGIN_DECLS +struct btmeta *btreadmeta(char *); +void btfreemeta(struct btmeta *); +__END_DECLS + +#endif /* not _META_H_ */ + diff --git a/netio.c b/netio.c new file mode 100644 index 0000000..6fc4d4b --- /dev/null +++ b/netio.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "meta.h" +#include "files.h" +#include "peer.h" +#include "netio.h" + +LIST_HEAD(btclist, btchunk) chead; +LIST_HEAD(btrlist, btrequest) rhead; + +struct btrequest *allocrequest(struct btmeta *, struct btpeer *, int, off_t, off_t); +int freerequest(struct btmeta *, struct btpeer *, int n, off_t, off_t); +struct btchunk *allocchunk(struct btmeta *, int); +int freechunk(struct btmeta *, int); + +/* DON'T FORGET: + +LIST_INIT(&chead); +LIST_INIT(&rhead); + + */ + +struct btrequest * +btallocrequest(struct btmeta *mp, struct btpeer *p, int n, off_t off, off_t len) +{ + struct btrequest *r; + + r = malloc(sizeof(struct btrequest)); + assert(r); + + r->peer = p; + r->offset = off; + r->length = len; + r->chunk = allocchunk(mp, n); + r->transmitted = 0; + + LIST_INSERT_HEAD(&rhead, r, link); + + return r; +} + +int +btfreerequest(struct btmeta *mp, struct btpeer *p, int n, off_t off, off_t len) +{ + struct btrequest *r, *nxt; + + for (r = LIST_FIRST(&rhead); r != LIST_END(&rhead); r = nxt) { + nxt = LIST_NEXT(r, link); + if (r->peer == p && r->offset == off && r->length == len) { + if (freechunk(mp, r->chunk->piece) == -1) + return -1; + LIST_REMOVE(r, link); + free(r); + return 0; + } + } + return -1; +} + +int +btwriterequest(struct btpeer *p, int n, off_t off, off_t len) +{ + struct btrequest *r, *nxt; + + for (r = LIST_FIRST(&rhead); r != LIST_END(&rhead); r = nxt) { + nxt = LIST_NEXT(r, link); + if (r->peer == p && r->offset == off && r->length == len) { + memcpy(r->chunk->data + r->offset, p->msgbuf, p->buflen); + return 0; + } + } + return -1; +} + +struct btchunk * +allocchunk(struct btmeta *mp, int n) +{ + struct btchunk *p; + + /* look if we have already that chunk */ + LIST_FOREACH(p, &chead, link) { + if (p->piece == n) { + ++p->ref; + return p; + } + } + + /* not found, allocate a new one */ + p = malloc(sizeof(struct btchunk)); + assert(p); + p->ref = 1; + p->piece = n; + p->sha1 = mp->pieces[n]; + p->len = btsizeofpiece(mp, n); + p->data = calloc(p->len, sizeof(char)); + assert(p->data); + if (btisset(mp->bitfield, n)) + btread(mp, p->data, p->len, n); + + return p; +} + +int +freechunk(struct btmeta *mp, int n) +{ + struct btchunk *p, *nxt; + + for (p = LIST_FIRST(&chead); p != LIST_END(&chead); p = nxt) { + nxt = LIST_NEXT(p, link); + if (p->piece == n) { + /* do we have it already? */ + if (btisset(mp->bitfield, n)) + return 1; + + /* check and write */ + if (!btcheck(p->sha1, p->data, p->len) || !btwrite(mp, p->data, p->len, n)) + return -1; + btsetbit(mp->bitfield, n); + + if (--p->ref <= 0) { + /* nobody refers anymore to this piece */ + LIST_REMOVE(p, link); + free(p->data); + free(p); + } + + /* announce all that we have that piece */ + return 0; + } + } + + /* nothing found */ + return -1; +} + + + + +#if 0 + +/* put message on queue */ +/* needed vars: + data, length, + send/recv (*fp)() + socket + */ + +void +btputqueue(struct btqueue *head, struct btpeer *p, ssize_t (*fp)(int, void *, size_t, int)) +{ + struct btqentry *q; + + q = malloc(sizeof(struct btqentry)); + assert(q); + + q->data = p->data + p->offset; + q->length = p->length; + q->doit = fp; + q->sockd = p->sockd; + + SIMPLEQ_INSERT_TAIL(head, q, link); +} + +int +nonblock(int s) +{ + int flags; + + flags = fcntl(s, F_GETFL); + if (flags < 0 || fcntl(s, F_SETFL, flags|O_NONBLOCK) < 0) + return -1; + + return s; +} + +#endif diff --git a/netio.h b/netio.h new file mode 100644 index 0000000..1728d4f --- /dev/null +++ b/netio.h @@ -0,0 +1,28 @@ +struct btchunk { /* rbuf: request buffer */ + LIST_ENTRY(btchunk) link; + int ref; /* how many peers points here */ + int piece; /* requested piece numer */ + u_char *sha1; + size_t len; + char *data; + + int nblk; /* bitfield of blocks ? */ + int blkdone; /* increment by read or write + since that both operations + cannot be simultan it isn't + a problem */ +}; + +// LIST_HEAD(btplist, btpiece); + +struct btrequest { + LIST_ENTRY(btrequest) link; + struct btpeer *peer; + struct btchunk *chunk; + size_t offset; + size_t length; + size_t transmitted; /* howmany is already read/written */ + +}; + +// LIST_HEAD(btrlist, btrequest); diff --git a/peer.c b/peer.c new file mode 100644 index 0000000..d8078b1 --- /dev/null +++ b/peer.c @@ -0,0 +1,487 @@ +/* $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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "meta.h" +#include "peer.h" /* not used */ +#include "tools.h" /* peerid() */ +#include "files.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +#define HSLEN 68 +#define PROTO "BitTorrent protocol" +#define PROTOLEN 19 +#define FLAGSLEN 8 + +// LIST_HEAD(btplist, btpeer) phead; + +char * +btgethandshake(const u_char *info, const u_char *id) +{ + /* Handshake ::= */ + static char buf[HSLEN]; + int len, off; + + off = 0; len = 1; + memset(buf, PROTOLEN, len); + + off += len; len = PROTOLEN; + memcpy(buf + off, PROTO, len); + + off += len; len = FLAGSLEN; + memset(buf + off, 0, len); + + off += len; len = SHA1LEN; + memcpy(buf + off, info, len); + + off += len; len = SHA1LEN; + if (id == NULL) + memset(buf + off, 0, len); + else + memcpy(buf + off, id, len); + + return buf; +} + +int +btchkhandshake(const char *src, const u_char *info, const u_char *id) +{ + char buf[HSLEN], *etalon; + + if (src == NULL) + return 0; + + /* ignore flags */ + memcpy(buf, src, HSLEN); + memset(buf + PROTOLEN + 1, 0, FLAGSLEN); + etalon = btgethandshake(info, id); + + return (memcmp(buf, etalon, id == NULL ? HSLEN - SHA1LEN : HSLEN) == 0); +} + +/**********************************************************************/ + +int +btaddpeer(struct btplist *plist, struct btpeer *peer) +{ + struct btpeer *p; + + LIST_FOREACH(p, plist, link) { + if (memcmp(&peer->sin, &p->sin, sizeof(struct sockaddr_in)) == 0) { + /* found same peer, reset its ttl value */ + p->ttl = MAX_TTL; + return 1; + } + } + + /* initial values */ + setbit(&peer->flags, LOCAL_CHOKED); + clrbit(&peer->flags, LOCAL_INTERESTED); + setbit(&peer->flags, REMOTE_CHOKED); + clrbit(&peer->flags, REMOTE_INTERESTED); + peer->ttl = MAX_TTL; + peer->state = IDLE; + LIST_INSERT_HEAD(plist, peer, link); + + return 0; +} + +void +btdumpplist(struct btplist *plist) +{ + struct btpeer *p; + int i = 0; + + LIST_FOREACH(p, plist, link) { + printf("%2d: %s:%d (%d) %c%c:%c%c\n", + ++i, + inet_ntoa(p->sin.sin_addr), + ntohs(p->sin.sin_port), + p->ttl, + "cC"[!!isset(&p->flags, LOCAL_CHOKED)], + "iI"[!!isset(&p->flags, LOCAL_INTERESTED)], + "cC"[!!isset(&p->flags, REMOTE_CHOKED)], + "iI"[!!isset(&p->flags, REMOTE_INTERESTED)] + ); + } +} + +int +btclearoldpeers(struct btplist *plist) +{ + struct btpeer *p, *nxt; + int n = 0; + + for (p = LIST_FIRST(plist); p != LIST_END(plist); p = nxt) { + nxt = LIST_NEXT(p, link); + if (--p->ttl <= 0 && p->state == FAILED) { + LIST_REMOVE(p, link); + free(p); + } else + ++n; + } + + return n; +} + +void +btdelplist(struct btplist *plist) +{ + struct btpeer *p, *nxt; + + for (p = LIST_FIRST(plist); p != LIST_END(plist); p = nxt) { + nxt = LIST_NEXT(p, link); + free(p); + } + LIST_INIT(plist); +} + +void +btdbg(struct btpeer *p, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s:%d ", + inet_ntoa(p->sin.sin_addr), + ntohs(p->sin.sin_port)); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +/**********************************************************************/ + +// newpeer() +/* alloc space, alloc 68 bytes for msgbuf (handshake length) */ + +#if 0 +int +chkhandshake(struct btpeer *p) +{ + char buf[20]; + + read(p->sockd, buf, 1); + if (*buf != 19) + drop(p); + read(p->sockd, buf, 19); + if (memcmp(buf, "Bittorrent Protocol", 19) != 0) + drop(p); + read(p->sockd, buf, 8); + read(p->sockd, buf, 20); + if (memcmp(buf, mp->infohash, 20) != 0) + drop(p); + read(p->sockd, buf, 20); + memcpy(p->peerid, buf, 20); + +// nonblock(p->sockid); + return 0; +} + + +int +readmsg(struct btpeer *p) +{ + size_t len, ret; + + if (p->msgbuf == NULL) { + read(p->sockd, &len, sizeof(size_t)); + p->buflen = ntohl(len); + p->msgbuf = malloc(p->buflen); + p->bufoff = 0; + } else { + ret = read(p->sockd, p->msgbuf + p->bufoff, p->buflen - p->bufoff); + p->bufoff += ret; + } + + if (p->msgbuf != NULL && p->buflen - p->bufoff == 0) { + dispatch(p); + free(p->msgbuf); + p->msgbuf = NULL; + } +} + +int +dispatch(struct btmeta *mp, struct btpeer *p) +{ + int piece, offset, length; + + switch (p->state) { + case IDLE: + case CONNECTING: + case HANDSHAKE: + case SUCCESS: + switch (*p->msgbuf) { + case MSG_CHOKE: + setbit(&p->flags, REMOTE_CHOKED); + break; + case MSG_UNCHOKE: + clrbit(&p->flags, REMOTE_CHOKED); + break; + case MSG_INTERESTED: + setbit(&p->flags, REMOTE_INTERESTED); + break; + case MSG_UNINTERESTED: + clrbit(&p->flags, REMOTE_INTERESTED); + break; + case MSG_HAVE: + piece = htonl(*(int *)&p->msgbuf[1]); + btsetbit(p->bitfield, piece); + + sendhave(); + + break; + case MSG_BITFIELD: + memcpy(p->bitfield, &p->msgbuf[1], p->buflen); + break; + case MSG_REQUEST: + piece = ntohl(*(int *)&p->msgbuf[1]); + offset = ntohl(*(int *)&p->msgbuf[5]); + length = ntohl(*(int *)&p->msgbuf[9]); + + btallocrequest(mp, p, piece, offset, length); + break; + case MSG_PIECE: + piece = ntohl(*(int *)&p->msgbuf[1]); + offset = ntohl(*(int *)&p->msgbuf[5]); + length = p->length - 9; + + btwriterequest(p, piece, offset, length, &p->msgbuf[9]); + btfreerequest(mp, p, piece, offset, length); + break; + case MSG_CANCEL: + piece = ntohl(*(int *)&p->msgbuf[1]); + offset = ntohl(*(int *)&p->msgbuf[5]); + length = ntohl(*(int *)&p->msgbuf[9]); + btfreerequest(mp, p, piece, offset, length); + break; + } + break; + case FAILED: + } +} + + +#endif + +/**********************************************************************/ + +/* messages: +-1 keep alive | length int | + 0 choke | length int | id char | + 1 unchoke | length int | id char | + 2 interested | length int | id char | + 3 uninterested | length int | id char | + 4 bitfield | length int | id char | bitfield bfsz | + 5 have | length int | id char | piece int | + 6 request | length int | id char | piece int | offset int | length int | + 7 piece | length int | id char | piece int | offset int | data blksz | + 8 cancel | length int | id char | piece int | offset int | lenght int | + */ + +char * +btmessage(struct btmeta *m, struct btpeer *p, size_t *mlen) +{ + size_t off; + int tmp; + char *buf; + + *mlen = sizeof(int); + if (p->msg >= MSG_CHOKE) + *mlen += sizeof(char); + if (p->msg == MSG_BITFIELD) + *mlen += m->bitlen; + if (p->msg >= MSG_HAVE) + *mlen += sizeof(int); + if (p->msg >= MSG_REQUEST) { + *mlen += sizeof(int); + if (p->msg == MSG_PIECE) + *mlen += BLOCKSZ; + else + *mlen += sizeof(int); + } + + buf = calloc(*mlen, sizeof(char)); + if (buf == NULL) + return NULL; + + tmp = htonl(*mlen - sizeof(int)); + memcpy(buf, &tmp, sizeof(int)); + off = sizeof(int); + + if (p->msg >= MSG_CHOKE) { + memcpy(buf + off, &p->msg, sizeof(char)); + off += sizeof(char); + } + if (p->msg == MSG_BITFIELD) + memcpy(buf + off, m->bitfield, m->bitlen); + if (p->msg >= MSG_HAVE) { + tmp = htonl(p->piece); + memcpy(buf + off, &tmp, sizeof(int)); + off += sizeof(int); + } + if (p->msg >= MSG_REQUEST) { + tmp = htonl(p->offset); + memcpy(buf + off, &tmp, sizeof(int)); + off += sizeof(int); + if (p->msg == MSG_PIECE) + memcpy(buf + off, p->data, BLOCKSZ); + else { + tmp = htonl(p->length); + memcpy(buf + off, &tmp, sizeof(int)); + } + } + + return buf; +} + +void +handlemsg(struct btmeta *mp, struct btpeer *p, char *buf, size_t buflen) +{ + size_t length; + enum msg msg; + int piece, offset, blklen; + + length = ntohl(*(int *)buf); + buf += sizeof(int); + + if (length == 0) + return; + + msg = *(char *)buf; + buf += sizeof(char); + length -= sizeof(char); + + switch (msg) { + case MSG_CHOKE: + setbit(&p->flags, REMOTE_CHOKED); + break; + case MSG_UNCHOKE: + clrbit(&p->flags, REMOTE_CHOKED); + break; + case MSG_INTERESTED: + setbit(&p->flags, REMOTE_INTERESTED); + break; + case MSG_UNINTERESTED: + clrbit(&p->flags, REMOTE_INTERESTED); + break; + case MSG_HAVE: + assert(p->bitfield); + piece = htonl(*(int *)buf); + btsetbit(p->bitfield, piece); + break; + case MSG_BITFIELD: + if (p->bitfield == NULL) { + p->bitfield = calloc(length, sizeof(char)); + assert(p->bitfield); + } + memcpy(p->bitfield, buf, length); + break; + case MSG_REQUEST: + piece = htonl(*(int *)buf); + buf += sizeof(int); + offset = htonl(*(int *)buf); + buf += sizeof(int); + blklen = htonl(*(int *)buf); + + if (p->data == NULL) { + p->data = calloc(mp->plength, sizeof(char)); + assert(p->data); + // btread(mp, p->data, piece); + } + + /* XXX send, put on queue */ + + break; + case MSG_PIECE: + piece = htonl(*(int *)buf); + buf += sizeof(int); + offset = htonl(*(int *)buf); + blklen = buflen - sizeof(int) * 2; + + if (p->data == NULL) { + p->data = calloc(mp->plength, sizeof(char)); + assert(p->data); + } + + /* XXX recieve, put on queue */ + + break; + case MSG_CANCEL: + piece = htonl(*(int *)buf); + buf += sizeof(int); + offset = htonl(*(int *)buf); + buf += sizeof(int); + blklen = htonl(*(int *)buf); + + /* XXX remove from queue */ + + break; + default: + break; + } +} + +#if 0 +payload(enum msg *msg) +{ + switch (msg) { + case MSG_CHOKE: + case MSG_UNCHOKE: + case MSG_INTERESTED: + case MSG_UNINTERESTED: + buf[0] = (int)4; + buf[3] = (char)msg; + } +} + +btsndchoked(struct btpeer *p) +{ + /* | length | id */ + int buf[2]; + + p->msg = MSG_CHOKE; + + buf[0] = sizeof(p->msg); + buf[1] = p->msg; +} + +btsndhave(struct btpeer *p) +{ + int buf[3]; + p->msg = MSG_HAVE; + + buf[0] = sizeof(p->msg) + sizeof(p->piece); + buf[1] = p->msg; + buf[2] = p->piece; +} +#endif diff --git a/peer.h b/peer.h new file mode 100644 index 0000000..9eccbe3 --- /dev/null +++ b/peer.h @@ -0,0 +1,130 @@ +/* $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. + */ + +#ifndef _PEER_H_ +#define _PEER_H_ + +#if 0 +#include +#endif + +#if 0 +snum stage { + PIECELESS, /* threshold 5% */ + NORMAL, + ENDGAME, /* threshold 95% */ + SEEDER +}; +#endif + +enum msg { + MSG_KEEPALIVE = -1, /* no payload at all */ + MSG_CHOKE, + MSG_UNCHOKE, + MSG_INTERESTED, + MSG_UNINTERESTED, + MSG_HAVE, + MSG_BITFIELD, + MSG_REQUEST, + MSG_PIECE, + MSG_CANCEL +}; + +/* peer wire message */ +struct btpwm { + u_int length; + u_char id; + void *payload; +}; + +enum state { + IDLE, + CONNECTING, + HANDSHAKE, + SUCCESS, + FAILED +}; + +#define MAX_TTL 10 +#define BLOCKSZ (16<10) + +struct btpeerstat { /* isn't used jet */ + unsigned int choked:1; + unsigned int interested:1; +}; + +enum flags { + LOCAL_CHOKED, + LOCAL_INTERESTED, + REMOTE_CHOKED, + REMOTE_INTERESTED, + OPTIMISTIC_CHOKING +}; + +struct btpeer { + LIST_ENTRY(btpeer) link; +#if 1 + int sockd; + struct sockaddr_in sin; +#else + struct btsock sock; +#endif + u_char peerid[SHA1LEN]; /* isn't known in `compact' mode */ + enum msg msg; /* ??? local/remote */ + enum state state; + int ttl; /* TODO: choose resonable value */ + char flags; /* chocked / interested / ... */ + char *bitfield; /* alloc space to hold bitfield and + data arrays on successful connect */ +#if 1 /* obsolete */ + /* request part */ + int piece; + off_t offset; + size_t length; + void *data_in; /* holds unfinished parts of piece */ + void *data_out; + void *data; +#endif + + char *msgbuf; + size_t buflen; + size_t bufoff; +// size_t expect; /* expect to read */ + + /* statistics */ + int dl; /* download rate */ + int ul; /* upload rate */ + int rank; +}; + +/* todo: read first 4 bytes, allocate space in msgbuf, read entry message, + decide what to do with it */ + +LIST_HEAD(btplist, btpeer); + +__BEGIN_DECLS +char *btgethandshake(const u_char *, const u_char *); +int btchkhandshake(const char *, const u_char *, const u_char *); + +void btdumpplist(struct btplist *); +int btaddpeer(struct btplist *, struct btpeer *); +int btclearoldpeers(struct btplist *); +void btdelplist(struct btplist *); +void btdbg(struct btpeer *, char *, ...); +__END_DECLS + +#endif /* not _PEER_H_ */ diff --git a/tools.c b/tools.c new file mode 100644 index 0000000..d9264f6 --- /dev/null +++ b/tools.c @@ -0,0 +1,230 @@ +/* $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 +#include /* struct timeval */ +#include +#include +#include +#include +#include /* setsockopt() */ + +#include +#include /* open() */ +#include +#include +#include /* strlcat() */ +#include /* sysconf() */ +#include "meta.h" /* SHA1LEN */ +#include "tools.h" +#include "version.h" + +#ifndef lint +static const char rcsid[] = "@(#)$Id$"; +#endif /* not lint */ + +int +raisenofile(void) +{ + struct rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { + rl.rlim_cur = rl.rlim_max; + return setrlimit(RLIMIT_NOFILE, &rl); + } + return -1; +} + +/* usable file desctriptors */ +int +nofile(void) +{ + struct rlimit rl; + struct stat st; + int i, ret; + + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { + ret = i = rl.rlim_cur; + while (i) + if (!fstat(--i, &st)) + --ret; + return ret; + } + return -1; +} + + +int +bthexdump(char *dst, u_char *src, int len) +{ + /* src is a SHA1 hash and is 20 bytes long + * dst should be at least 41 bytes long for a simple hexdump, + * and 61 bytes long for a quoted printable + * (including the terminating `\0' character) + */ + int i, done; + char *fmt; + + if (len <= 2 * SHA1LEN) + return -1; + + for (i = SHA1LEN, done = 0, *dst = NULL; i > 0; i--) { + if (len > 3 * SHA1LEN) { + if (isascii(*src) && (isalnum(*src) || + *src == '-' || *src == '.' || + *src == '_' || *src == '~')) + fmt = "%c"; + else + fmt = "%%%.2X"; + } else + fmt = "%.2x"; + done += snprintf(dst + done, len - done, fmt, *src++); + } + + return done; +} + +char * +btfreadall(char *name) +{ + FILE *fd; + char *buf; + off_t len; + + if ((fd = fopen(name, "r")) == NULL) + return NULL; + + fseek(fd, 0L, SEEK_END); + len = ftell(fd); + fseek(fd, 0L, SEEK_SET); + + if ((buf = calloc(len, sizeof(char))) != NULL) + if ((fread(buf, sizeof(char), len, fd)) == 0) { + free(buf); + return NULL; + } + + fclose(fd); + + return buf; +} + +#if MMAP +void * +btmmapfile(char *name, off_t *len) +{ + struct stat stat; + int fd; + off_t page; + void *buf; + + if ((fd = open(name, O_RDONLY)) == -1) { + perror("open"); + return NULL; + } + + if (fstat(fd, &stat) == -1) { + perror("fstat"); + close(fd); + return NULL; + } + + page = sysconf(_SC_PAGESIZE); + *len = stat.st_size; + if (*len == 0) + return NULL; + *len += page - (*len % page); + + if ((buf = mmap((void *) NULL, *len, PROT_READ, MAP_SHARED, fd, 0L)) == NULL) { + perror("mmap"); + close(fd); + return NULL; + } + + close(fd); + + return buf; +} + +void +btunmapfile(void *buf, off_t len) +{ + munmap(buf, len); +} +#endif /* MMAP */ + +void +btpeerid(u_char *str) +{ + const char hex[16] = "0123456789ABCDEF"; + int i; + + /* we only use unreserved characters (RFC 3986) as PEER_ID, + * so we don't need to percent-encode them and + * it's still random enough (16^12 possibilities) + * OBSOLETE since we use array to store that value + */ + snprintf((char *) str, 9, "-XB%.2d%.2d-", VERSION_MAJOR, VERSION_MINOR); + for (i = 8; i < SHA1LEN; i++) + str[i] = hex[arc4random() % sizeof(hex)]; +} + +__dead void +usage(const char *usagearg) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s %s\n", __progname, usagearg); + exit(1); +} + +int +setrcvtimeo(int s, int msec) +{ + struct timeval tv; + + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec % 1000) * 1000; + + return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +} + +void +twiddle(void) +{ + static int pos; + + putchar("|/-\\"[pos++ & 3]); + putchar('\b'); +} + +char * +metric(double len) +{ + static char buf[16]; + char *units[] = { + "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL + }; + int n = 0; + + for (n = 0; len >= 1024 && n < sizeof(units) / sizeof(units[0]); n++) + len /= 1024; + + snprintf(buf, sizeof(buf), "%.2f %s", len, units[n]); + + return buf; +} diff --git a/tools.h b/tools.h new file mode 100644 index 0000000..4ed4717 --- /dev/null +++ b/tools.h @@ -0,0 +1,40 @@ +/* $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. + */ + +#ifndef _TOOLS_H_ +#define _TOOLS_H_ + +#define HASHSZS 41 /* 2 * SHA1LEN + 1 */ +#define HASHSZL 61 /* 3 * SHA1LEN + 1 */ + +__BEGIN_DECLS +int raisenofile(void); +int nofile(void); +int bthexdump(char *, u_char *, int); +char *btfreadall(char *); +#if MMAP +void *btmmapfile(char *, off_t *); +void btunmapfile(void *, off_t); +#endif /* MMAP */ +void btpeerid(u_char *); +__dead void usage(const char *); +int setrcvtimeo(int, int); +void twiddle(void); +char *metric(double); +__END_DECLS + +#endif /* not _TOOLS_H_ */ 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; +} diff --git a/tracker.h b/tracker.h new file mode 100644 index 0000000..4683715 --- /dev/null +++ b/tracker.h @@ -0,0 +1,74 @@ +/* $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. + */ + +#ifndef _TRACKER_H_ +#define _TRACKER_H_ + +#if 0 +#include +#endif + +enum compact { + COMP_NO, + COMP_YES +}; + +enum event { + EV_NONE, + EV_STARTED, + EV_COMPLETED, + EV_STOPPED +}; + +struct bttracker { + struct sockaddr_in sin; /* tracker */ + int listener; /* sockd */ + /* request part */ + struct btannounce *announce; + u_char *infohash; /* meta->infohash */ + u_char localid[SHA1LEN]; + u_char trackerid[SHA1LEN]; + char *key; /* ??? */ + int localport; /* my port, should be set by btstartserver() */ + + off_t uploaded; /* statistics */ + off_t downloaded; + off_t left; + + enum event event; + enum compact compact; + int numwait; + + /* answer part */ + struct btplist plist; /* LIST_HEAD */ + + long lastrqst; + int interval; + int minterval; + int complete; + int incomplete; + int npeers; +}; + +__BEGIN_DECLS +struct bttracker *btinittracker(struct btmeta *, int); +int btcalltracker(struct bttracker *); +void btupdatetracker(struct bttracker *, int, int, enum event); +void btfreetracker(struct bttracker *); +__END_DECLS + +#endif /* not _TRACKER_H_ */ diff --git a/version.h b/version.h new file mode 100644 index 0000000..3c4d834 --- /dev/null +++ b/version.h @@ -0,0 +1,9 @@ +/* $Id$ */ +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#define USERAGENT "XBitTorrent" +#define VERSION_MAJOR 1 +#define VERSION_MINOR 0 + +#endif /* Not _VERSION_H_ */ -- cgit v1.2.3