#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" "Hypercube tracker status\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n", num_peers, num_seeders, num_leechers, num_torrents, announce_count, scrape_count, status_count, peers_count, (unsigned int)(time(NULL) - last_period), PEER_HASH_SIZE, sizeof(torrent_hash), peers_mem_size); if (akbuf_table_entry_get(http_ent->args, "rate") != NULL) { unsigned long long total_rate, cur_rate; unsigned int i; peer_entry *peer; total_rate = 0; for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) { while (peer != NULL) { unsigned long long delta; if (peer->lastevent != EVENT_STARTED && peer->lastevent != EVENT_STOPPED && peer->lastevent != EVENT_COMPLETED && peer->prev_active != (time_t)0 && peer->last_active != (time_t)0) { delta = peer->last_active - peer->prev_active; if (delta != 0 && peer->uploaded != 0 && peer->prev_uploaded != 0 && peer->downloaded != 0 && peer->prev_downloaded != 0 && peer->uploaded > peer->prev_uploaded && peer->downloaded > peer->prev_downloaded) { cur_rate = (peer->uploaded - peer->prev_uploaded + peer->downloaded - peer->prev_downloaded) / delta; if (cur_rate < 10485760) total_rate += cur_rate; } } peer = peer->next; } } total_rate /= 1048576; akbuf_appendf(net_ent->sendbuf, "\n", total_rate); } if (akbuf_table_entry_get(http_ent->args, "show_torrents") != NULL) { akbuf_ctxh ctx; akbuf *buf; unsigned int i; peer_entry *peer; ctx = akbuf_new_ctx(); buf = akbuf_init(ctx, 0); akbuf_appendf(net_ent->sendbuf, "\n"); for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) { akbuf_set_idx(buf, 0); akbuf_urlencode_data(peer->info_hash, ID_LEN, buf); akbuf_asciiz(buf); DEBUGF("html urlenc: last byte %.2x str [%s]", peer->info_hash[ID_LEN - 1], akbuf_data(buf)); akbuf_appendf(net_ent->sendbuf, "\n" "\n" "\n" "\n" "\n", akbuf_data(buf), peer->num_seeders, peer->num_leechers, peer->times_completed, get_date_str(peer->last_active), peer->hash_idx); } akbuf_free_ctx(ctx); } else { akbuf_appendf(net_ent->sendbuf, "\n"); } akbuf_appendf(net_ent->sendbuf, "
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/size0x%x/%u byte(s)
 Size of peers in memory%u byte(s)
 Total rate (MB/sec)%llu
TorrentsHide 
 Info hash%s
 Seeders/Leechers/Completed%u/%u/%u
 Last activity%s
 Peer hash bucket #0x%x
 ------------------------------- 
TorrentsShow 
\n\n\n"); } /* * GET /status?[rate]|[[norefresh]&[raw]&[reset]] * norefresh: don't refresh peers list * raw: raw format * reset: reset completed */ void tracker_serve_status(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) { status_count ++; if (cfg.tracker.sync == 1) do_sync(); if (akbuf_table_entry_get(http_ent->args, "norefresh") == NULL) refresh_peers(); if (akbuf_table_entry_get(http_ent->args, "raw") != NULL) { serve_status_raw(fd, http_ent, net_ent); } else { serve_status_html(fd, http_ent, net_ent); } } void scrape_out(peer_entry *peer, akbuf *outbuf) { akbuf_ctxh ctx; akbuf *buf; refresh_peer(peer); ctx = akbuf_new_ctx(); buf = akbuf_init(ctx, 0); benc_raw(outbuf, peer->info_hash, ID_LEN); benc_key_int(buf, "complete", peer->num_seeders); benc_key_int(buf, "downloaded", peer->times_completed); benc_key_int(buf, "incomplete", peer->num_leechers); benc_out_dict(buf, outbuf); akbuf_free_ctx(ctx); } void tracker_serve_scrape(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) { akbuf_ctxh ctx; akbuf *info_hash; akbuf *outbuf, *filesbuf, *buf; peer_entry *peer; unsigned int i; #define SCRAPE_ERR(msg)\ {\ benc_key(outbuf, "failure reason", (msg));\ tracker_benc_response(fd, http_ent, net_ent, outbuf);\ akbuf_free_ctx(ctx);\ return;\ } scrape_count ++; ctx = akbuf_new_ctx(); outbuf = akbuf_init(ctx, 0); filesbuf = akbuf_init(ctx, 0); buf = akbuf_init(ctx, 0); if ((info_hash = akbuf_table_entry_get(http_ent->args, "info_hash")) == NULL) { for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) { akbuf_set_idx(buf, 0); scrape_out(peer, filesbuf); peer = peer->next; } } else { if (akbuf_idx(info_hash) != ID_LEN) SCRAPE_ERR("invalid info_hash"); if ((peer = torrent_hash[PEER_HASH_FN(info_hash)]) != NULL) { /* XXX hash colls */ akbuf_set_idx(buf, 0); scrape_out(peer, filesbuf); } } benc_str(outbuf, "files"); benc_out_dict(filesbuf, outbuf); tracker_benc_response(fd, http_ent, net_ent, outbuf); akbuf_free_ctx(ctx); } void tracker_serve_announce(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) { akbuf_ctxh ctx; akbuf *outbuf, *peerbuf, *peersbuf; akbuf *info_hash, *peer_id, *ipbuf, *portbuf, *ulbuf, *dlbuf, *leftbuf, *eventbuf; akbuf *uabuf; int i; unsigned int j, c; unsigned int curevent, do_compact; peer_entry *curpeer; struct in_addr ipaddr; #define AN_ERR(msg)\ {\ akbuf_set_idx(outbuf, 0);\ benc_key(outbuf, "failure reason", (msg));\ tracker_benc_response(fd, http_ent, net_ent, outbuf);\ akbuf_free_ctx(ctx);\ return;\ } announce_count ++; DEBUGF("tracker: doing announce"); ctx = akbuf_new_ctx(); outbuf = akbuf_init(ctx, 0); //benc_key_int(outbuf, "interval", cfg.tracker.interval); if ((info_hash = akbuf_table_entry_get(http_ent->args, "info_hash")) == NULL) AN_ERR("need info_hash"); #ifdef INFOHASH_RESTRICTION if (infohash_allowed[PEER_HASH_FN(info_hash)] == 0) AN_ERR("this tracker is for torrents on TPB only"); #endif if ((peer_id = akbuf_table_entry_get(http_ent->args, "peer_id")) == NULL) AN_ERR("need peer_id"); if ((portbuf = akbuf_table_entry_get(http_ent->args, "port")) == NULL) AN_ERR("need port"); if ((ulbuf = akbuf_table_entry_get(http_ent->args, "uploaded")) == NULL) AN_ERR("need uploaded"); if ((dlbuf = akbuf_table_entry_get(http_ent->args, "downloaded")) == NULL) AN_ERR("need downloaded"); if ((leftbuf = akbuf_table_entry_get(http_ent->args, "left")) == NULL) AN_ERR("need left"); ipbuf = akbuf_table_entry_get(http_ent->args, "ip"); eventbuf = akbuf_table_entry_get(http_ent->args, "event"); curevent = EVENT_NONE; if (eventbuf != NULL && akbuf_idx(eventbuf) > 0) { akbuf_asciiz(eventbuf); if (strncasecmp(akbuf_data(eventbuf), "sta", 3) == 0) curevent = EVENT_STARTED; else if (strncasecmp(akbuf_data(eventbuf), "com", 3) == 0) curevent = EVENT_COMPLETED; else if (strncasecmp(akbuf_data(eventbuf), "sto", 3) == 0) curevent = EVENT_STOPPED; } if (curevent != EVENT_NONE) DEBUGF("got event %u", curevent); if (akbuf_idx(info_hash) != ID_LEN || akbuf_idx(peer_id) != ID_LEN) AN_ERR("invalid info_hash and/or peer_id"); do_compact = (akbuf_table_entry_get(http_ent->args, "compact") != NULL)? 1 : 0; if (curevent == EVENT_STOPPED || curevent == EVENT_COMPLETED) { if ((curpeer = peer_get(peer_id, info_hash, 0)) == NULL) { DEBUGF("event %u for unknown peer, ignoring", curevent); benc_str(outbuf, "peers"); akbuf_appendf(outbuf, "le"); tracker_benc_response(fd, http_ent, net_ent, outbuf); akbuf_free_ctx(ctx); return; } } else { if ((curpeer = peer_get(peer_id, info_hash, 1)) == NULL) AN_ERR("too many peers"); } if (curevent == EVENT_STARTED || curpeer->lastevent == EVENT_STARTED) { benc_key_int(outbuf, "interval", cfg.tracker.init_interval); } else { benc_key_int(outbuf, "interval", cfg.tracker.interval); } curpeer->is_local = 1; j = 0; for (i = 0; i < akbuf_idx(portbuf); i ++) { akbuf_get_byte(portbuf, i, c); if (c < '0' || c > '9') break; j *= 10; j += c - '0'; } /* causes problems for some clients if (j > 0xffff || j < 0x400) AN_ERR("invalid port"); */ curpeer->port = j; if (ipbuf != NULL && akbuf_idx(ipbuf) > 1) { akbuf_asciiz(ipbuf); if ((ipaddr.s_addr = inet_addr(akbuf_data(ipbuf))) == INADDR_NONE) { ipbuf = NULL; } else { j = ntohl(ipaddr.s_addr); if ((j >= 0x0a000000 && j <= 0x0affffff) || (j >= 0xac100000 && j <= 0xac1fffff) || (j >= 0xc0a80000 && j <= 0xc0a8ffff)) ipbuf = NULL; } } if (ipbuf == NULL || akbuf_idx(ipbuf) <= 1) { ipbuf = akbuf_init(ctx, 0); akbuf_clone(ipbuf, net_ent->peerbuf); if ((i = akbuf_chr(ipbuf, ':')) != -1) akbuf_set_idx(ipbuf, i); } akbuf_asciiz(ipbuf); AKstrcpy(curpeer->ipstr, akbuf_data(ipbuf)); j = curpeer->ipnum = ntohl(inet_addr(curpeer->ipstr)); curpeer->ipraw[0] = (j >> 24) & 0xff; curpeer->ipraw[1] = (j >> 16) & 0xff; curpeer->ipraw[2] = (j >> 8) & 0xff; curpeer->ipraw[3] = j & 0xff; curpeer->prev_active = curpeer->last_active; curpeer->last_active = time(NULL); #if 0 if (num_peers == 1000000) { FILE *f; if (cfg.tracker.statslog != NULL && (f = fopen(cfg.tracker.statslog, "a+")) != NULL) { fprintf(f, "!!! Reached 1 million peers @ %s: %s %u\n", get_now_date_str(), curpeer->ipstr, curpeer->port); fflush(f); fclose(f); } } #endif j = 0; for (i = 0; i < akbuf_idx(leftbuf); i ++) { akbuf_get_byte(leftbuf, i, c); if (c < '0' || c > '9') break; if (j >= 0x19999999) { j = (unsigned int)-1; break; } j *= 10; j += c - '0'; } DEBUGF("got left %u", j); curpeer->is_seeder = (j == 0)? 1 : 0; curpeer->prev_uploaded = curpeer->uploaded; curpeer->uploaded = 0; for (i = 0; i < akbuf_idx(ulbuf); i ++) { akbuf_get_byte(ulbuf, i, c); if (c < '0' || c > '9') break; curpeer->uploaded *= 10; curpeer->uploaded += c - '0'; } curpeer->prev_downloaded = curpeer->downloaded; curpeer->downloaded = 0; for (i = 0; i < akbuf_idx(dlbuf); i ++) { akbuf_get_byte(dlbuf, i, c); if (c < '0' || c > '9') break; curpeer->downloaded *= 10; curpeer->downloaded += c - '0'; } DEBUGF("ul %llu dl %llu", curpeer->uploaded, curpeer->downloaded); curpeer->lastevent = curevent; if ((uabuf = akbuf_table_entry_get(http_ent->headers, "User-Agent")) != NULL) { if (akbuf_idx(uabuf) >= 12 && memcmp(akbuf_data(uabuf), "Ratio Fucker", 12) == 0) { akbuf_asciiz(net_ent->peerbuf); aklogf(LOG_INFO, "Cheater: Ratio Fucker request from %s", akbuf_data(net_ent->peerbuf)); curpeer->uploaded = 0; curpeer->downloaded = 0; } } i = 0; peerbuf = akbuf_init(ctx, 0); peersbuf = akbuf_init(ctx, 0); if (curevent == EVENT_COMPLETED) { torrent_hash[curpeer->hash_idx]->times_completed ++; curpeer->is_complete = 1; } send_peers(peersbuf, curpeer, (curpeer->is_seeder == 1)? 0 : 1, do_compact); if (do_compact == 1) { benc_key_buf(outbuf, "peers", peersbuf); } else { benc_str(outbuf, "peers"); benc_out_list(peersbuf, outbuf); } tracker_benc_response(fd, http_ent, net_ent, outbuf); if (curevent != EVENT_STOPPED && cfg.tracker.sync == 1) sync_peer(curpeer); akbuf_free_ctx(ctx); } void update_stats(void) { #ifdef WITH_MYSQL MYSQL sql_conn; akbuf *buf, *sqlbuf; akbuf_ctxh ctx; unsigned int i; peer_entry *peer, *headpeer; if (cfg.tracker.sql_stats == 0) return; DEBUGF("doing update_stats()"); if (fork() != 0) return; signal(SIGALRM, exit); alarm(cfg.tracker.period); DEBUGF("in update_stats() child"); //while (1) sleep(1); mysql_init(&sql_conn); if (mysql_real_connect(&sql_conn, cfg.tracker.sql_host, cfg.tracker.sql_user, cfg.tracker.sql_pass, cfg.tracker.sql_db, 0, NULL, 0) == NULL) { aklogf(LOG_ERROR, "Couldn't connect to MySQL server: %s", mysql_error(&sql_conn)); exit(1); } ctx = akbuf_new_ctx(); buf = akbuf_init(ctx, 0); sqlbuf = akbuf_init(ctx, 0); akbuf_sprintf(sqlbuf, "UPDATE hc_stats SET seeders='%u', leechers='%u', num_torrents='%u'", num_seeders, num_leechers, num_torrents); akbuf_asciiz(sqlbuf); DEBUGF("sql query (hc_stats) [%s]", akbuf_data(sqlbuf)); if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) { DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); } for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = headpeer = torrent_hash[i]) != NULL) { akbuf_set_idx(buf, 0); akbuf_set_idx(sqlbuf, 0); akbuf_base64encode_data(peer->info_hash, ID_LEN, buf); akbuf_asciiz(buf); akbuf_sprintf(sqlbuf, "UPDATE torrents SET seeders='%u', leechers='%u', last_active='%u' WHERE info_hash='%s'", peer->num_seeders, peer->num_leechers, peer->last_active, akbuf_data(buf)); akbuf_asciiz(sqlbuf); DEBUGF("sql query (torrents) [%s]", akbuf_data(sqlbuf)); if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) { DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); } while (peer != NULL) { if (peer->is_complete == 1) { peer->is_complete = 0; akbuf_set_idx(buf, 0); akbuf_set_idx(sqlbuf, 0); akbuf_base64encode_data(peer->info_hash, ID_LEN, buf); akbuf_asciiz(buf); akbuf_sprintf(sqlbuf, "UPDATE torrents SET num_downloads=num_downloads+1 WHERE info_hash='%s'", akbuf_data(buf)); akbuf_asciiz(sqlbuf); DEBUGF("sql query (torrents complete) [%s]", akbuf_data(sqlbuf)); if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) { DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); } } else if (peer->lastevent == EVENT_STOPPED) { akbuf_set_idx(buf, 0); akbuf_set_idx(sqlbuf, 0); akbuf_base64encode_data(peer->info_hash, ID_LEN, buf); akbuf_asciiz(buf); akbuf_sprintf(sqlbuf, "UPDATE users SET uploaded=uploaded+'%llu', downloaded=downloaded+'%llu' WHERE ip='%u'", peer->uploaded, peer->downloaded, peer->ipnum); akbuf_asciiz(sqlbuf); DEBUGF("sql query (peers) [%s]", akbuf_data(sqlbuf)); if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) { DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); } else { peer->uploaded = peer->downloaded = 0; peer->prev_uploaded = peer->prev_downloaded = 0; } peer->uploaded = peer->downloaded = 0; peer->prev_uploaded = peer->prev_downloaded = 0; } peer = peer->next; } } akbuf_free_ctx(ctx); exit(0); #endif } void tracker_periodic(void) { time_t t; FILE *f; //scramble_peers(); #ifdef INFOHASH_RESTRICTION if ((t = time(NULL) - last_infohash_period) >= cfg.tracker.infohash_interval) { read_infohash_file(); last_infohash_period = time(NULL); } #endif if ((t = time(NULL) - last_period) < cfg.tracker.period) return; if (cfg.tracker.statslog != NULL && (f = fopen(cfg.tracker.statslog, "a+")) != NULL) { fprintf(f, "%s %u %u %u %u / %u\n", get_now_date_str(), announce_count, scrape_count, status_count, peers_count, (unsigned int)t); fflush(f); fclose(f); } announce_count = scrape_count = status_count = peers_count = 0; refresh_peers(); update_stats(); last_period = time(NULL); }