/*************************************************************************** * Copyright (C) 07/2007 by Olaf Rempel * * razzor@kopf-tisch.de * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; version 2 of the License * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "configfile.h" #include "event.h" #include "httpd.h" #include "linebuffer.h" #include "list.h" #include "logging.h" #include "sockaddr.h" #include "tcpsocket.h" #include "torrentfile.h" struct client_stats { /* current bandwidth up/down */ int bw_up; int bw_dn; /* total bytes up/down */ unsigned long long total_up; unsigned long long total_dn; /* clients cloud view */ int chunk_total; int chunk_avail; int chunk_have; }; struct client_con { struct list_head list; struct torrent_file *torrent; struct sockaddr_in addr; struct event_fd *event; struct linebuffer *lbuf; /* download stats */ struct client_stats stats; /* timestamp when this client completed */ long completed; }; static struct event_fd *listen_event; static struct event_fd *httpd_event; static struct event_timeout *timeout_event; static void free_client(struct client_con *con) { list_del(&con->list); /* remove torrents without clients */ if (list_empty(&con->torrent->client_list)) destroy_torrent(con->torrent); close(event_get_fd(con->event)); event_remove_fd(con->event); free(con->lbuf); free(con); } static int ctcs_data_handler(int fd, void *privdata) { struct client_con *con = (struct client_con *)privdata; struct client_stats *stats = &con->stats; /* get data from socket */ if (linebuffer_readfd(con->lbuf, fd) < 0) { free_client(con); return -1; } char *line; while ((line = linebuffer_getline(con->lbuf, NULL)) != NULL) { /* bandwidth update */ if (strncmp(line, "CTBW ", 5) == 0) { int bwup, bwdn, liup, lidn; if (sscanf(line +5, "%d,%d %d,%d", &bwdn, &bwup, &lidn, &liup) == 4) { stats->bw_up = bwup; stats->bw_dn = bwdn; } /* status update (assume protocol v2) */ } else if (strncmp(line, "CTSTATUS ", 9) == 0) { int seeds1 = 0, seeds2 = 0, leech1 = 0, leech2 = 0, count = 0; int chunk1 = 0, chunk2 = 0, chunk3 = 0, bwdn = 0, bwup = 0; int lidn = 0, liup = 0, cache = 0; unsigned long long totdn = 0, totup = 0; if (sscanf(line +9, "%d:%d/%d:%d/%d %d/%d/%d %d,%d %llu,%llu %d,%d %d", &seeds1, &seeds2, &leech1, &leech2, &count, &chunk1, &chunk2, &chunk3, &bwdn, &bwup, &totdn, &totup, &lidn, &liup, &cache) == 15) { stats->bw_up = bwup; stats->bw_dn = bwdn; stats->total_up = totup; stats->total_dn = totdn; stats->chunk_have = chunk1; stats->chunk_total = chunk2; stats->chunk_avail = chunk3; } /* update torrent-file */ } else if (strncmp(line, "CTORRENT ", 9) == 0) { char *filename = strrchr(line +9, ' '); if (filename != NULL && strcmp(filename, con->torrent->name) != 0) { struct torrent_file *torrent = find_create_torrent(filename +1); if (torrent != NULL) { list_del(&con->list); list_add_tail(&con->list, &torrent->client_list); con->torrent = torrent; } } } linebuffer_freeline(con->lbuf); } /* client completed? */ if (stats->chunk_have == stats->chunk_total && stats->chunk_total != 0 && con->completed == 0) { con->completed = time(NULL); list_del(&con->list); /* sorted insert, completed on top of list, ordered by complete-timestamp */ struct client_con *search; list_for_each_entry(search, &con->torrent->client_list, list) { if (search->completed == 0) break; if (search->completed > con->completed) break; } list_add_tail(&con->list, &search->list); return 0; } return 0; } static int ctcs_trigger_status(void *privdata) { long timeout = time(NULL) - (int)privdata; struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { int seeder = 0; int leecher = 0; struct client_con *con; list_for_each_entry(con, &torrent->client_list, list) { write(event_get_fd(con->event), "SENDSTATUS\n", 11); if (con->completed) seeder++; else leecher++; } /* no seeders available and no local seeder running => spawn one */ if (leecher > 0 && seeder == 0 && torrent->child == NULL) seed_torrent(torrent); /* delete holds the number of clients to quit */ int delete = seeder - leecher; list_for_each_entry(con, &torrent->client_list, list) { if (delete <= 0) break; if (con->completed == 0 || con->completed > timeout) continue; write(event_get_fd(con->event), "CTQUIT\n", 7); delete--; } } return 0; } static int ctcs_accept_handler(int fd, void *privdata) { struct client_con *con = malloc(sizeof(struct client_con)); if (con == NULL) { log_print(LOG_WARN, "accept_cb(): out of memory"); return 0; } memset(con, 0, sizeof(struct client_con)); con->lbuf = create_linebuffer(1024); if (con->lbuf == NULL) { log_print(LOG_WARN, "accept_cb(): out of memory"); free(con); return 0; } int sockfd = tcp_accept(fd, &con->addr); if (sockfd < 0) { log_print(LOG_WARN, "accept_cb(): tcp_accept()"); free(con->lbuf); free(con); return 0; } con->event = event_add_readfd(NULL, sockfd, ctcs_data_handler, con); /* assign default torrent */ con->torrent = find_create_torrent("[unknown]"); list_add_tail(&con->list, &con->torrent->client_list); return 0; } static int ctcs_httpd_show(struct httpd_con *con, void *privdata) { struct linebuffer *lbuf = create_linebuffer(16384); if (lbuf == NULL) { httpd_send_error(con, "500 ERROR", "Out of Memory"); return -1; } linebuffer_printf(lbuf, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"); linebuffer_printf(lbuf, "

ctorrent stats

\n"); struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { if (list_empty(&torrent->client_list)) continue; linebuffer_printf(lbuf, "\n\n", torrent->name); linebuffer_printf(lbuf, ""); linebuffer_printf(lbuf, "\n"); struct client_con *tmp; list_for_each_entry(tmp, &torrent->client_list, list) { struct client_stats *stats = &tmp->stats; linebuffer_printf(lbuf, "", get_sockaddr_buf(&tmp->addr), (double)stats->chunk_have / (double)stats->chunk_total * 100.0, stats->chunk_have, stats->chunk_total, stats->chunk_avail); linebuffer_printf(lbuf, "", stats->total_dn, stats->bw_dn, stats->total_up, stats->bw_up, (tmp->completed != 0) ? ctime(&tmp->completed) : "-"); linebuffer_printf(lbuf, "\n", get_sockaddr_buf(&tmp->addr)); } linebuffer_printf(lbuf, "
%s
Client IP:PortChunks (have/total/avail)Download total(current)Upload total(current)Completed sinceQuit
%s%3.2lf%% (%d/%d/%d)%llu (%d)%llu (%d)%sQuit
\n

\n"); } linebuffer_printf(lbuf, "\n"); linebuffer_writefd(lbuf, con->fd); linebuffer_free(lbuf); return 0; } static int ctcs_httpd_quit(struct httpd_con *con, void *privdata) { if (con->req_arg_cnt == 2 && strncmp(con->req_args[1], "client=", 7) == 0) { struct sockaddr_in addr; if (parse_sockaddr(con->req_args[1] +7, &addr) == 0) { struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { struct client_con *search; list_for_each_entry(search, &torrent->client_list, list) { if (!same_sockaddr(&search->addr, &addr)) continue; write(event_get_fd(search->event), "CTQUIT\n", 7); } } } } char *text = "HTTP/1.0 302 OK\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: /\r\n\r\n"; write(con->fd, text, strlen(text)); return 0; } int ctcs_init(void) { const char *listen_addr = config_get_string("global", "listen", "0.0.0.0:2780"); listen_event = tcp_listen_event(listen_addr, ctcs_accept_handler, NULL); if (listen_event == NULL) return -1; const char *httpd_addr = config_get_string("global", "listen-http", "0.0.0.0:8080"); httpd_event = tcp_listen_event(httpd_addr, httpd_accept_handler, NULL); if (httpd_event == NULL) { event_remove_fd(listen_event); close(event_get_fd(listen_event)); return -1; } httpd_add_cb("/quit", 0, ctcs_httpd_quit, NULL); httpd_add_cb("/", 1, ctcs_httpd_show, NULL); int interval = config_get_int("global", "status-interval", 10); int timeout = config_get_int("global", "seed-timeout", 60); struct timeval tv = { .tv_sec = interval, .tv_usec = 0 }; timeout_event = event_add_timeout(&tv, ctcs_trigger_status, (void *)timeout); if (timeout_event == NULL) { event_remove_fd(httpd_event); close(event_get_fd(httpd_event)); event_remove_fd(listen_event); close(event_get_fd(listen_event)); return -1; } return 0; } int ctcs_free(void) { struct torrent_file *torrent, *torrent2; list_for_each_entry_safe(torrent, torrent2, &torrent_list, list) { struct client_con *con, *con2; list_for_each_entry_safe(con, con2, &torrent->client_list, list) { free_client(con); } } event_remove_timeout(timeout_event); event_remove_fd(httpd_event); close(event_get_fd(httpd_event)); httpd_free(); event_remove_fd(listen_event); close(event_get_fd(listen_event)); return 0; }