#include "hypercube.h" extern hypercube_config cfg; peer_entry *torrent_hash[PEER_HASH_SIZE]; peer_entry *peer_hash[PEER_HASH_SIZE]; #ifdef INFOHASH_RESTRICTION unsigned int infohash_allowed[PEER_HASH_SIZE]; static time_t last_infohash_period; #endif unsigned int num_peers, num_torrents, num_seeders, num_leechers; unsigned int peers_mem_size; unsigned int announce_count, scrape_count, status_count, peers_count; static time_t last_period; static akbuf_ctxh tracker_ctx; akbuf *syncbuf; void benc_str(akbuf *bencbuf, unsigned char *str) { akbuf_appendf(bencbuf, "%u:%s", strlen(str), str); } void benc_int(akbuf *bencbuf, int i) { akbuf_appendf(bencbuf, "i%de", i); } void benc_raw(akbuf *bencbuf, unsigned char *data, AKsize_t len) { akbuf_appendf(bencbuf, "%u:", len); akbuf_append_data(bencbuf, data, len); } void benc_buf(akbuf *bencbuf, akbuf *buf) { benc_raw(bencbuf, akbuf_data(buf), akbuf_idx(buf)); } void benc_key_raw(akbuf *bencbuf, unsigned char *key, unsigned char *val, AKsize_t val_len) { benc_str(bencbuf, key); benc_raw(bencbuf, val, val_len); } void benc_key_buf(akbuf *bencbuf, unsigned char *key, akbuf *valbuf) { benc_str(bencbuf, key); benc_buf(bencbuf, valbuf); } void benc_key_int(akbuf *bencbuf, unsigned char *key, int val) { benc_str(bencbuf, key); benc_int(bencbuf, val); } void benc_key(akbuf *bencbuf, unsigned char *key, unsigned char *val) { benc_str(bencbuf, key); benc_str(bencbuf, val); } void benc_out_dict(akbuf *bencbuf, akbuf *outbuf) { akbuf_append_byte(outbuf, 'd'); akbuf_append_buf(outbuf, bencbuf); akbuf_append_byte(outbuf, 'e'); } void benc_out_list(akbuf *bencbuf, akbuf *outbuf) { akbuf_append_byte(outbuf, 'l'); akbuf_append_buf(outbuf, bencbuf); akbuf_append_byte(outbuf, 'e'); } void tracker_http_response(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent, unsigned int code, unsigned char *content_type) { net_send(fd); http_ent->keep_alive = 0; if (http_ent->ver_maj == 0) return; akbuf_sprintf(net_ent->sendbuf, "HTTP/%u.%u %u %s\r\n" "Server: " SERVER_VERSION "\r\n" "Content-type: %s\r\n" "Connection: close\r\n" "Pragma: no-cache\r\n" "\r\n", http_ent->ver_maj, http_ent->ver_min, code, http_status_msg(code), content_type); } void tracker_benc_response(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent, akbuf *bencbuf) { tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/plain"); benc_out_dict(bencbuf, net_ent->sendbuf); #ifdef DEBUG { unsigned int i, c; for (i = 0; i < akbuf_idx(bencbuf); i ++) { akbuf_get_byte(bencbuf, i, c); if (c == 0) akbuf_set_byte(bencbuf, i, '.'); } akbuf_asciiz(bencbuf); DEBUGF("sending bencoded response (%u byte(s)): [%s]\n", akbuf_idx(bencbuf), akbuf_data(bencbuf)); } #endif } unsigned int hash_buf(akbuf *buf) { unsigned int i, c; unsigned char curhash[4]; memcpy(curhash, "\xf0\x0f\xc7\xc8", 4); for (i = 0; i < akbuf_idx(buf); i ++) { akbuf_get_byte(buf, i, c); curhash[i & 3] ^= c; } return (curhash[0] << 24) | (curhash[1] << 16) | (curhash[2] << 8) | curhash[3]; } peer_entry *peer_get(akbuf *peer_id, akbuf *info_hash, unsigned int alloc_new) { peer_entry *peer, *cur; unsigned int idx, pidx, i, free_idx; AKdassert(akbuf_idx(peer_id) == ID_LEN && akbuf_idx(info_hash) == ID_LEN); pidx = PEER_HASH_FN(peer_id); if (peer_hash[pidx] != NULL && memcmp(peer_hash[pidx]->peer_id, akbuf_data(peer_id), ID_LEN) == 0 && memcmp(peer_hash[pidx]->info_hash, akbuf_data(info_hash), ID_LEN) == 0) { DEBUGF("found peer in peer_hash"); return peer_hash[pidx]; } free_idx = (unsigned int)-1; idx = PEER_HASH_FN(info_hash); peer = NULL; for (i = 0; i < PEER_HASH_SEARCH_DELTA; i ++) { if ((cur = torrent_hash[idx]) == NULL && free_idx == (unsigned int)-1) free_idx = idx; if (cur != NULL && memcmp(cur->info_hash, akbuf_data(info_hash), ID_LEN) == 0) { peer = cur; break; } idx ++; if (idx >= PEER_HASH_SIZE) idx = 0; } if (i == PEER_HASH_SEARCH_DELTA) { if (free_idx == (unsigned int)-1) return NULL; idx = free_idx; } else { while (peer != NULL) { if (memcmp(peer->peer_id, akbuf_data(peer_id), ID_LEN) == 0) break; peer = peer->next; } } if (peer == NULL && alloc_new == 1) { peer = malloc(sizeof(peer_entry)); peers_mem_size += sizeof(peer_entry); num_peers ++; peer->num_hits = peer->num_seeders = peer->num_leechers = peer->times_completed = 0; peer->uploaded = peer->downloaded = 0; peer->prev_uploaded = peer->prev_downloaded = 0; peer->lastevent = 0; peer->last_active = peer->prev_active = (time_t)0; peer->is_complete = 0; memcpy(peer->peer_id, akbuf_data(peer_id), ID_LEN); memcpy(peer->info_hash, akbuf_data(info_hash), ID_LEN); peer->hash_idx = idx; peer->peer_hash_idx = pidx; peer->prev = NULL; if ((peer->next = torrent_hash[idx]) != NULL) peer->next->prev = peer; torrent_hash[idx] = peer; peer_hash[pidx] = peer; } return peer; } void peer_del(peer_entry *peer) { if (peer_hash[peer->peer_hash_idx] == peer) peer_hash[peer->peer_hash_idx] = NULL; if (peer->prev != NULL) peer->prev->next = peer->next; if (peer->next != NULL) peer->next->prev = peer->prev; if (peer == torrent_hash[peer->hash_idx]) { if (peer->next != NULL) { peer->next->num_seeders = peer->num_seeders; peer->next->num_leechers = peer->num_leechers; peer->next->times_completed = peer->times_completed; } torrent_hash[peer->hash_idx] = peer->next; } num_peers --; free(peer); peers_mem_size -= sizeof(peer_entry); } void scramble_peers(void) { unsigned int i, j, newpeerscount; peer_entry *peer, *prevpeer, *headpeer; DEBUGF("scrambling peers\n"); for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = headpeer = torrent_hash[i]) != NULL) { if ((headpeer->num_seeders + headpeer->num_leechers) > 1) // cfg.tracker.respnum) { newpeerscount = 0; peer = headpeer; j = rand() % ((headpeer->num_seeders + headpeer->num_leechers) >> 1); while (j > 0 && peer != NULL) { peer = peer->next; j --; } if (peer == NULL || peer == headpeer) continue; while (peer != NULL && newpeerscount < cfg.tracker.respnum) { prevpeer = peer; peer = peer->next; if ((rand() & 7) == 3) { if (prevpeer->prev != NULL) prevpeer->prev->next = prevpeer->next; if (prevpeer->next != NULL) prevpeer->next->prev = prevpeer->prev; prevpeer->next = headpeer->next; prevpeer->prev = headpeer; headpeer->next = prevpeer; newpeerscount ++; } } } } } void send_peers(akbuf *outbuf, peer_entry *curpeer, unsigned int send_seeders, unsigned int do_compact) { peer_entry *peer, *headpeer, *sendpeers[cfg.tracker.respnum]; unsigned int num_sendpeers, i, j; akbuf_ctxh ctx; akbuf *buf; for (i = 0; i < cfg.tracker.respnum; i ++) sendpeers[i] = NULL; num_sendpeers = 0; peer = headpeer = torrent_hash[curpeer->hash_idx]; if (headpeer != NULL && (j = headpeer->num_seeders + headpeer->num_leechers) >= cfg.tracker.respnum * 2) { for (i = rand() % (j - cfg.tracker.respnum); i > 0; i --) { if ((peer = peer->next) == NULL) peer = headpeer; } } while (num_sendpeers < cfg.tracker.respnum && peer != NULL) { if (peer != curpeer) { for (i = 0; i < cfg.tracker.respnum; i ++) if (sendpeers[i] == peer) break; if (i == cfg.tracker.respnum && (send_seeders == 1 || peer->is_seeder == 0)) { j = rand() & (cfg.tracker.respnum - 1); if (sendpeers[j] == NULL) { sendpeers[j] = peer; num_sendpeers ++; peer->num_hits ++; } } } peer = peer->next; } ctx = akbuf_new_ctx(); buf = akbuf_init(ctx, 0); for (i = 0; i < cfg.tracker.respnum; i ++) { if (sendpeers[i] != NULL) { akbuf_set_idx(buf, 0); if (do_compact == 1) { akbuf_append_data(outbuf, sendpeers[i]->ipraw, sizeof(sendpeers[i]->ipraw)); akbuf_append_byte(outbuf, (sendpeers[i]->port >> 8) & 0xff); akbuf_append_byte(outbuf, sendpeers[i]->port & 0xff); } else { benc_key(buf, "ip", sendpeers[i]->ipstr); benc_key_raw(buf, "peer id", sendpeers[i]->peer_id, ID_LEN); benc_key_int(buf, "port", sendpeers[i]->port); benc_out_dict(buf, outbuf); } sendpeers[i] = NULL; } } akbuf_free_ctx(ctx); } /* * synchronization packet: * each entry: * [magic 'AKhc' 4 bytes] * [info hash 20 bytes][peer id 20 bytes] * [ip addr 4 bytes][port 2 bytes] * [is seeder 1 byte] * [padding 1 byte] * total entry len: 48 bytes */ #define SYNC_ENTRY_LEN (2 + ID_LEN * 2 + 4 + 2 + 1 + 1) #define SYNC_ENTRY_MAGIC "AKhc" #define SYNC_ENTRY_MAGIC_LEN 4 void handle_sync_packet(int fd, net_fd_entry *net_ent, void *dummy) { ssize_t n; struct sockaddr_in fromsin; socklen_t sin_len; akbuf_ctxh ctx; akbuf *buf; akbuf *peer_id, *info_hash; unsigned char ipraw[4]; unsigned int ipnum, port; unsigned int is_seeder; peer_entry *peer; struct in_addr in; DEBUGF("got sync packet"); ctx = akbuf_new_ctx(); buf = akbuf_init(ctx, cfg.tracker.sync_size + 100); sin_len = sizeof(fromsin); if ((n = recvfrom(fd, akbuf_data(buf), akbuf_size(buf), 0, (struct sockaddr *)&fromsin, &sin_len)) <= 0) { akperror("tracker.c:handle_sync_packet():recvfrom()"); akbuf_free_ctx(ctx); return; } akbuf_set_idx(buf, n); DEBUGF("got sync packet from %s:%u", inet_ntoa(fromsin.sin_addr), ntohs(fromsin.sin_port)); DEBUGF("sync packet is %u bytes", akbuf_idx(buf)); peer_id = akbuf_init(ctx, ID_LEN); info_hash = akbuf_init(ctx, ID_LEN); while (akbuf_idx(buf) >= SYNC_ENTRY_LEN) { if (memcmp(akbuf_data(buf), SYNC_ENTRY_MAGIC, SYNC_ENTRY_MAGIC_LEN) != 0) { DEBUGF("malformed sync entry, invalid magic %.2x%.2x%.2x%.2x", akbuf_data(buf)[0], akbuf_data(buf)[1], akbuf_data(buf)[2], akbuf_data(buf)[3]); break; } akbuf_consume(buf, SYNC_ENTRY_MAGIC_LEN); akbuf_set_data(info_hash, akbuf_data(buf), ID_LEN); akbuf_consume(buf, ID_LEN); akbuf_set_data(peer_id, akbuf_data(buf), ID_LEN); akbuf_consume(buf, ID_LEN); ipraw[0] = akbuf_eat_byte(buf); ipraw[1] = akbuf_eat_byte(buf); ipraw[2] = akbuf_eat_byte(buf); ipraw[3] = akbuf_eat_byte(buf); port = (akbuf_eat_byte(buf) << 8) | akbuf_eat_byte(buf); is_seeder = akbuf_eat_byte(buf); akbuf_eat_byte(buf); /* eat padding */ DEBUGF("sync entry: ipraw %.2x%.2x%.2x%.2x port %x is_seeder %u", ipraw[0], ipraw[1], ipraw[2], ipraw[3], port, is_seeder); if ((peer = peer_get(peer_id, info_hash, 1)) == NULL) { aklogf(LOG_ERROR, "tracker.c:handle_sync_packet(): peer_get() failed!"); } else { peer->last_active = time(NULL); peer->lastevent = 0; peer->is_local = 0; peer->is_seeder = is_seeder; memcpy(peer->ipraw, ipraw, 4); peer->port = port; ipnum = (ipraw[0] << 24) | (ipraw[1] << 16) | (ipraw[2] << 8) | ipraw[3]; in.s_addr = peer->ipnum = htonl(ipnum); AKstrcpy(peer->ipstr, inet_ntoa(in)); DEBUGF("peer->ipstr = '%s' peer->port = %u", peer->ipstr, peer->port); DEBUGF("torrent hash idx %u peer hash idx %u", peer->hash_idx, peer->peer_hash_idx); } } if (akbuf_idx(buf) > 0) { DEBUGF("malformed sync entry, %u byte(s) left", akbuf_idx(buf)); } akbuf_free_ctx(ctx); } #ifdef INFOHASH_RESTRICTION void read_infohash_file(void) { unsigned int i; int fd; akbuf *readbuf; akbuf_ctxh ctx; for (i = 0; i < PEER_HASH_SIZE; i ++) infohash_allowed[i] = 0; if (cfg.tracker.infohash_file == NULL || (fd = open(cfg.tracker.infohash_file, O_RDONLY)) == -1) { for (i = 0; i < PEER_HASH_SIZE; i ++) infohash_allowed[i] = 1; return; } ctx = akbuf_new_ctx(); readbuf = akbuf_init(ctx, ID_LEN); akbuf_set_idx(readbuf, ID_LEN); while (read(fd, akbuf_data(readbuf), ID_LEN) == ID_LEN) { infohash_allowed[PEER_HASH_FN(readbuf)] = 1; } close(fd); akbuf_free_ctx(ctx); } void init_infohash_restriction(void) { unsigned int i; last_infohash_period = 0; for (i = 0; i < PEER_HASH_SIZE; i ++) infohash_allowed[i] = 1; //read_infohash_file(); } #endif void init_sync(void) { int s; struct sockaddr_in sin; syncbuf = akbuf_init(tracker_ctx, cfg.tracker.sync_size); if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 || s >= ASIO_MAX_FDS) { akperror("tracker.c:init_sync():socket()"); return; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(cfg.tracker.sync_port); if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) { akperror("tracker.c:init_sync():bind()"); return; } net_set_fd(s, NET_FD_RAW, handle_sync_packet, NULL, 0); aklogf(LOG_INFO, "Tracker sync listening on UDP port %u", cfg.tracker.sync_port); } void do_sync(void) { int s; struct sockaddr_in sin; if (akbuf_idx(syncbuf) == 0) return; if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { akperror("tracker.c:do_sync():socket()"); return; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = 0; if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) { akperror("tracker.c:do_sync():bind()"); close(s); return; } sin.sin_addr.s_addr = cfg.tracker.sync_addr.s_addr; sin.sin_port = htons(cfg.tracker.sync_port); DEBUGF("syncing %u bytes", akbuf_idx(syncbuf)); if (sendto(s, akbuf_data(syncbuf), akbuf_idx(syncbuf), 0, (struct sockaddr *)&sin, sizeof(sin)) != akbuf_idx(syncbuf)) { akperror("tracker.c:do_sync():sendto()"); close(s); return; } akbuf_set_idx(syncbuf, 0); DEBUGF("sent sync packet"); } void sync_peer(peer_entry *peer) { akbuf_append_str(syncbuf, SYNC_ENTRY_MAGIC); akbuf_append_data(syncbuf, peer->info_hash, ID_LEN); akbuf_append_data(syncbuf, peer->peer_id, ID_LEN); akbuf_append_data(syncbuf, peer->ipraw, sizeof(peer->ipraw)); akbuf_append_byte(syncbuf, (peer->port >> 8) & 0xff); akbuf_append_byte(syncbuf, peer->port & 0xff); akbuf_append_byte(syncbuf, peer->is_seeder & 0xff); akbuf_append_byte(syncbuf, 42); if (akbuf_idx(syncbuf) >= cfg.tracker.sync_size) do_sync(); } void tracker_init(void) { unsigned int i; tracker_ctx = akbuf_new_ctx(); num_peers = num_torrents = num_seeders = num_leechers = 0; peers_mem_size = 0; last_period = time(NULL); announce_count = scrape_count = status_count = peers_count = 0; for (i = 0; i < PEER_HASH_SIZE; i ++) torrent_hash[i] = peer_hash[i] = NULL; if (cfg.tracker.sync == 1) init_sync(); #ifdef INFOHASH_RESTRICTION init_infohash_restriction(); #endif } void refresh_peer(peer_entry *peer) { peer_entry *headpeer; headpeer = peer; headpeer->num_seeders = headpeer->num_leechers = 0; while (peer != NULL) { if (peer->is_seeder == 1) { num_seeders ++; headpeer->num_seeders ++; } else { num_leechers ++; headpeer->num_leechers ++; } peer = peer->next; } } void refresh_peers(void) { unsigned int i; peer_entry *peer, *headpeer, *prev; num_seeders = num_leechers = 0; num_torrents = 0; for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = headpeer = torrent_hash[i]) != NULL) { num_torrents ++; headpeer->num_seeders = headpeer->num_leechers = 0; while (peer != NULL) { time_t t; if (peer->is_seeder == 1) { num_seeders ++; headpeer->num_seeders ++; } else { num_leechers ++; headpeer->num_leechers ++; } prev = peer; peer = peer->next; t = time(NULL) - prev->last_active; if ((prev->lastevent != EVENT_STOPPED && t >= cfg.tracker.timeout) || (prev->lastevent == EVENT_STOPPED && t >= cfg.tracker.stopped_timeout)) { peer_del(prev); } } } } /* * GET /peers?[reset] * reset: reset ul/dl */ void tracker_serve_peers(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) { unsigned int i; peer_entry *peer; unsigned int do_reset, only_stopped; peers_count ++; /* * [info hash]:[ip]:[uploaded]:[downloaded]:[last event]\n */ do_reset = (akbuf_table_entry_get(http_ent->args, "reset") != NULL)? 1 : 0; only_stopped = (akbuf_table_entry_get(http_ent->args, "stopped") != NULL)? 1 : 0; tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/plain"); for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) { while (peer != NULL) { if (only_stopped == 0 || peer->lastevent == EVENT_STOPPED) { akbuf_urlencode_data(peer->info_hash, ID_LEN, net_ent->sendbuf); akbuf_append_byte(net_ent->sendbuf, ':'); akbuf_appendf(net_ent->sendbuf, "%s:%llu:%llu:", peer->ipstr, peer->uploaded, peer->downloaded); akbuf_appendf(net_ent->sendbuf, "%u", peer->lastevent); akbuf_append_byte(net_ent->sendbuf, '\n'); if (do_reset == 1) peer->uploaded = peer->downloaded = peer->prev_uploaded = peer->prev_downloaded = 0; } peer = peer->next; } } } void serve_status_raw(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) { unsigned int i, do_reset; peer_entry *peer; /* * [info hash]:[num seeders]:[num leechers]:[times completed]:[unixtime of last activity]\n * ...repeat ad nauseam... * the info hash is ID_LEN bytes raw data, the rest are unsigned base10 ints */ tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/plain"); do_reset = (akbuf_table_entry_get(http_ent->args, "reset") != NULL)? 1 : 0; for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) { akbuf_urlencode_data(peer->info_hash, ID_LEN, net_ent->sendbuf); akbuf_append_byte(net_ent->sendbuf, ':'); akbuf_appendf(net_ent->sendbuf, "%u:%u:%u:", peer->num_seeders, peer->num_leechers, peer->times_completed); if (do_reset == 1) peer->times_completed = 0; akbuf_appendf(net_ent->sendbuf, "%u", (unsigned int)peer->last_active); akbuf_append_byte(net_ent->sendbuf, '\n'); } } void serve_status_html(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) { tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/html"); akbuf_appendf(net_ent->sendbuf, "\n" "
Statistics | ||
Number of peers/seeders/leechers | %u/%u/%u | |
Number of torrents | %u | |
Number of announce/scrape/status/peers | %u/%u/%u/%u in %u sec(s) | |
Tracker info | ||
Version | " SERVER_VERSION " | |
Peer hash table buckets/size | 0x%x/%u byte(s) | |
Size of peers in memory | %u byte(s) | |
Total rate (MB/sec) | %llu | |
Torrents | Hide | |
Info hash | %s | |
Seeders/Leechers/Completed | %u/%u/%u | |
Last activity | %s | |
Peer hash bucket # | 0x%x | |
------------------------------- | ||
Torrents | Show |