diff --git a/Makefile b/Makefile index 8eb05dc..de5e6aa 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CFLAGS := -O2 -pipe -Wall -OBJS := configfile.o event.o linebuffer.o logging.o sockaddr.o tcpsocket.o +OBJS := configfile.o connection.o event.o httpd.o linebuffer.o logging.o sockaddr.o tcpsocket.o all: torrent-stats diff --git a/connection.c b/connection.c new file mode 100644 index 0000000..ede9aad --- /dev/null +++ b/connection.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include + +#include "event.h" +#include "httpd.h" +#include "linebuffer.h" +#include "list.h" +#include "logging.h" +#include "sockaddr.h" +#include "tcpsocket.h" + +static LIST_HEAD(client_list); + +struct client_con { + struct list_head list; + + struct sockaddr_in addr; + struct event_timeout *trigger; + struct event_fd *event; + struct linebuffer *lbuf; + + int bw_up; + int bw_dn; + + unsigned long long total_up; + unsigned long long total_dn; + + int chunk_total; + int chunk_avail; + int chunk_have; +}; + +static void free_client(struct client_con *con) +{ + close(event_get_fd(con->event)); + event_remove_fd(con->event); + event_remove_timeout(con->trigger); + free(con->lbuf); + free(con); +} + +static int trigger_status(void *privdata) +{ + struct client_con *con = (struct client_con *)privdata; + write(event_get_fd(con->event), "SENDSTATUS\n", 11); + return 0; +} + +static int data_cb(int fd, void *privdata) +{ + struct client_con *con = (struct client_con *)privdata; + + if (linebuffer_read(con->lbuf, fd) < 0) { + log_print(LOG_WARN, "data_cb(): client %s read", get_sockaddr_buf(&con->addr)); + list_del(&con->list); + free_client(con); + return -1; + } + + char *line; + while ((line = linebuffer_get(con->lbuf)) != NULL) { + if (strncmp(line, "CTBW ", 5) == 0) { + int bwup, bwdn, liup, lidn; + if (sscanf(line +5, "%d,%d %d,%d", &bwdn, &bwup, &lidn, &liup) == 4) { + con->bw_up = bwup; + con->bw_dn = bwdn; + } + + } 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) { + + con->total_up = totup; + con->total_dn = totdn; + con->chunk_have = chunk1; + con->chunk_total = chunk2; + con->chunk_avail = chunk3; + } + } + free(line); + } + return 0; +} + +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; + } + + unsigned int i = sizeof(con->addr); + int sockfd = accept(fd, (struct sockaddr *)&con->addr, &i); + if (sockfd < 0) { + log_print(LOG_WARN, "accept_cb(): accept()"); + free(con->lbuf); + free(con); + return 0; + } + + struct timeval tv = { .tv_sec = 10, .tv_usec = 0 }; + con->trigger = event_add_timeout(&tv, trigger_status, con); + + con->event = event_add_readfd(NULL, sockfd, data_cb, con); + + list_add(&con->list, &client_list); + return 0; +} + +int ctcs_httpd_handler(struct httpd_con *con, void *privdata) +{ + int size = 4096, len = 0; + char *text = malloc(size); + if (text == NULL) { + httpd_send_error(con, "500 ERROR", "Out of Memory"); + return -1; + } + + len += snprintf(text + len, size - len, "

ctorrent stats

\n\n"); + len += snprintf(text + len, size - len, "\n"); + + struct client_con *tmp; + list_for_each_entry(tmp, &client_list, list) { + len += snprintf(text + len, size - len, "\n", + get_sockaddr_buf(&tmp->addr), + (double)tmp->chunk_have / (double)tmp->chunk_total * 100.0, tmp->chunk_have, tmp->chunk_total, tmp->chunk_avail, + tmp->total_dn, tmp->bw_dn, tmp->total_up, tmp->bw_up); + } + + len += snprintf(text + len, size - len, "
Client IP:PortChunks (have/total/avail)Download total(current)Upload total(current)
%s%3.2lf%% (%d/%d/%d)%llu (%d)%llu (%d)
\n\n"); + + httpd_send_header(con, "200 OK", "text/html"); + write(con->fd, text, len); + return 0; +} diff --git a/connection.h b/connection.h new file mode 100644 index 0000000..8ab26cc --- /dev/null +++ b/connection.h @@ -0,0 +1,9 @@ +#ifndef _CONNECTION_H_ +#define _CONNECTION_H_ + +#include "httpd.h" + +int ctcs_accept_handler(int fd, void *privdata); +int ctcs_httpd_handler(struct httpd_con *con, void *privdata); + +#endif /* _CONNECTION_H_ */ diff --git a/httpd.c b/httpd.c new file mode 100644 index 0000000..f44395a --- /dev/null +++ b/httpd.c @@ -0,0 +1,226 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "event.h" +#include "httpd.h" +#include "list.h" +#include "logging.h" +#include "sockaddr.h" +#include "tcpsocket.h" + +static LIST_HEAD(httpd_cb_list); + +static void destroy_httpd_con(struct httpd_con *con) +{ + close(event_get_fd(con->event)); + event_remove_fd(con->event); + + if (con->req_data != NULL) + free(con->req_data); + + if (con->req_headers != NULL) + free(con->req_headers); + + if (con->req_args != NULL) + free(con->req_args); + + free(con); +} + +int httpd_send_header(struct httpd_con *con, char *code, char *type) +{ + char buf[256]; + int len = snprintf(buf, sizeof(buf), "HTTP/1.0 %s\r\nContent-Type: %s\r\nConnection: close\r\n\r\n", code, type); + write(con->fd, buf, len); + return 0; +} + +int httpd_send_error(struct httpd_con *con, char *code, char *msg) +{ + httpd_send_header(con, code, "text/plain"); + write(con->fd, msg, strlen(msg)); + return 0; +} + +static int parse_headers(struct httpd_con *con) +{ + char *data = con->req_data; + while ((data = strstr(data, "\r\n")) && data < con->req_data + con->req_size) { + con->req_header_cnt++; + data += 2; + } + + con->req_headers = malloc(con->req_header_cnt * sizeof(char *)); + if (con->req_headers == NULL) + return -1; + + int i = 0; + data = con->req_data; + + do { + con->req_headers[i++] = data; + + data = strstr(data, "\r\n"); + if (data == NULL) + break; + + *data++ = '\0'; + *data++ = '\0'; + + } while (data < con->req_data + con->req_size); + return 0; +} + +static int parse_args(struct httpd_con *con) +{ + /* seperate GET (in req->data) */ + char *req = strchr(con->req_data, ' '); + *req++ = '\0'; + + /* HTTP/1.1 will go to header[0] */ + char *tmp = strchr(req, ' '); + *tmp++ = '\0'; + con->req_headers[0] = tmp; + + /* count args */ + tmp = req; + while (*tmp != '\0' && tmp < con->req_data + con->req_size) { + if (*tmp == '?' || *tmp == '&') + con->req_arg_cnt++; + + tmp++; + } + con->req_arg_cnt++; + + /* alloc args */ + con->req_args = malloc(con->req_arg_cnt * sizeof(char *)); + if (con->req_args == NULL) + return -1; + + int i = 0; + tmp = req; + + con->req_args[i++] = tmp; + while (*tmp != '\0' && tmp < con->req_data + con->req_size) { + if (*tmp == '?' || *tmp == '&') { + *tmp++ = '\0'; + con->req_args[i++] = tmp; + } + tmp++; + } + return 0; +} + +static int httpd_content_handler(int fd, void *privdata) +{ + struct httpd_con *con = (struct httpd_con *)privdata; + + if (ioctl(fd, FIONREAD, &con->req_size) == -1) { + log_print(LOG_WARN, "http_content_handler(): ioctl(FIONREAD)"); + destroy_httpd_con(con); + return -1; + } + + con->req_data = malloc(con->req_size); + if (con->req_data == NULL) { + log_print(LOG_WARN, "http_content_handler(): out of memory"); + destroy_httpd_con(con); + return -1; + } + + con->req_size = read(fd, con->req_data, con->req_size); + if (con->req_size <= 0) { + log_print(LOG_WARN, "http_content_handler(): read()"); + destroy_httpd_con(con); + return -1; + } + + if (strncmp(con->req_data, "GET ", 4) != 0) { + httpd_send_error(con, "400 Invalid Request", "Not a GET request\r\n"); + destroy_httpd_con(con); + return -1; + } + + if (parse_headers(con) < 0) { + httpd_send_error(con, "400 Invalid Request", "Error parsing headers\r\n"); + destroy_httpd_con(con); + return -1; + } + + if (parse_args(con) < 0) { + httpd_send_error(con, "400 Invalid Request", "Error parsing arguments\r\n"); + destroy_httpd_con(con); + return -1; + } + + struct httpd_callback *entry; + list_for_each_entry(entry, &httpd_cb_list, list) { + + if ((entry->wildcard == 1 && strncmp(entry->name, con->req_args[0], strlen(entry->name)) == 0) || + (entry->wildcard == 0 && strcmp(entry->name, con->req_args[0]) == 0)) { + + entry->callback(con, entry->privdata); + destroy_httpd_con(con); + return 0; + } + } + + httpd_send_error(con, "404 Not Found", "File not found\r\n"); + destroy_httpd_con(con); + return 0; +} + +int httpd_accept_handler(int fd, void *privdata) +{ + struct httpd_con *con = malloc(sizeof(struct httpd_con)); + if (con == NULL) { + log_print(LOG_WARN, "httpd_accept_handler(): out of memory"); + return 0; + } + + memset(con, 0, sizeof(struct httpd_con)); + + unsigned int i = sizeof(con->addr); + con->fd = accept(fd, (struct sockaddr *)&con->addr, &i); + if (con->fd < 0) { + free(con); + return 0; + } + + con->event = event_add_readfd(NULL, con->fd, httpd_content_handler, con); + return 0; +} + +struct httpd_callback * httpd_add_cb(const char *name, + int wildcard, + int (* callback)(struct httpd_con *con, void *privdata), + void *privdata) +{ + struct httpd_callback *hcb = malloc(sizeof(struct httpd_callback)); + if (hcb == NULL) { + log_print(LOG_WARN, "httpd_create_cb(): out of memory"); + return NULL; + } + + hcb->name = strdup(name); + hcb->wildcard = wildcard; + hcb->callback = callback; + hcb->privdata = privdata; + + list_add_tail(&hcb->list, &httpd_cb_list); + return hcb; +} + +int httpd_remove_cb(struct httpd_callback *hcb) +{ + list_del(&hcb->list); + free(hcb->name); + free(hcb); + return 0; +} diff --git a/httpd.h b/httpd.h new file mode 100644 index 0000000..82df687 --- /dev/null +++ b/httpd.h @@ -0,0 +1,42 @@ +#ifndef _HTTPD_H_ +#define _HTTPD_H_ + +#include + +#include "event.h" +#include "list.h" + +struct httpd_con { + struct sockaddr_in addr; + struct event_fd *event; + + int fd; + + char *req_data; + unsigned int req_size; + + char **req_headers; + unsigned int req_header_cnt; + + char **req_args; + unsigned int req_arg_cnt; +}; + +struct httpd_callback { + struct list_head list; + char *name; + int wildcard; + + int (* callback)(struct httpd_con *con, void *privdata); + void *privdata; +}; + +int httpd_send_header(struct httpd_con *con, char *code, char *type); +int httpd_send_error(struct httpd_con *con, char *code, char *msg); + +struct httpd_callback * httpd_add_cb(const char *name, int flags, int (* cb)(struct httpd_con *con, void *privdata), void *privdata); +int httpd_remove_cb(struct httpd_callback *cb); + +int httpd_accept_handler(int fd, void *privdata); + +#endif // _HTTP_H_ diff --git a/torrent-stats.c b/torrent-stats.c index e25cab4..9703a51 100644 --- a/torrent-stats.c +++ b/torrent-stats.c @@ -1,212 +1,16 @@ #include #include #include -#include #include "configfile.h" +#include "connection.h" #include "event.h" -#include "linebuffer.h" +#include "httpd.h" #include "list.h" #include "logging.h" #include "sockaddr.h" #include "tcpsocket.h" -/* -startup: -PROTOCOL 0002 -CTORRENT -CD0300-0xECFCD73A6132281994AF75C3 1179237904 1179239410 debian-40r0-i386-netinst.iso.torrent -CTSTATUS 0:174/1:5/0 636/636/636 0,1070 169914368,638976 0,0 32 - -periodic: -CTBW 0,1070 0,0 - -------------------------- - -"SENDDETAIL": -CTDETAIL 166621184 262144 1179239468 1179238848 -CTFILES -CTFILE 1 636 636 636 166621184 debian-40r0-i386-netinst.iso -CTFDONE - -"CTDETAIL %lld %d %ld %ld", -BTCONTENT.GetTotalFilesLength(), -(int)(BTCONTENT.GetPieceLength()), -(long)now, -(long)(BTCONTENT.GetSeedTime()) ); - -"CTFILE %d %d %d %d %llu %s", -(int)n, -(int)(BTCONTENT.getFilePieces(n)), -(int)(tmpBitField.Count()), -(int)(tmpavail.Count()), -(unsigned long long)(file->bf_length), -file->bf_filename ); - -------------------------- - -"SENDSTATUS": -CTSTATUS 0:174/1:5/0 636/636/636 0,959 169914368,655360 0,0 32 - -"CTSTATUS %d:%d/%d:%d/%d %d/%d/%d %d,%d %llu,%llu %d,%d %d", -(int)(m_ctstatus.seeders), -(int)(Tracker.GetSeedsCount() - ((BTCONTENT.pBF->IsFull() && Tracker.GetSeedsCount() > 0) ? 1 : 0)), -(int)(m_ctstatus.leechers), -(int)(Tracker.GetPeersCount() - Tracker.GetSeedsCount() - ((!BTCONTENT.pBF->IsFull() && Tracker.GetPeersCount() > 0) ? 1 : 0)), -(int)(WORLD.GetConnCount()), - -(int)(m_ctstatus.nhave), -(int)(m_ctstatus.ntotal), -(int)(WORLD.Pieces_I_Can_Get()), - -(int)(m_ctstatus.dlrate), -(int)(m_ctstatus.ulrate), - -(unsigned long long)(m_ctstatus.dltotal), -(unsigned long long)(m_ctstatus.ultotal), - -(int)(m_ctstatus.dlimit), -(int)(m_ctstatus.ulimit), - -(int)(m_ctstatus.cacheused) ); - -------------------------- - -"SENDCONF": -CTCONFIG 0 72 0.000000 100 1 0 16 0 - -"SENDPEERS": -CTPEERS -CTPEER M3-4-2--bae925c0c070 159.148.184.187 UnUi 0 862 114688 688128 520 -CTPDONE -*/ - -static LIST_HEAD(client_list); - -struct client_con { - struct list_head list; - - struct sockaddr_in addr; - struct event_timeout *trigger; - struct event_fd *event; - struct linebuffer *lbuf; - - int bw_up; - int bw_dn; - - unsigned long long total_up; - unsigned long long total_dn; - - int chunk_total; - int chunk_avail; - int chunk_have; -}; - -static void free_client(struct client_con *con) -{ - close(event_get_fd(con->event)); - event_remove_fd(con->event); - event_remove_timeout(con->trigger); - free(con->lbuf); - free(con); -} - -static int trigger_status(void *privdata) -{ - struct client_con *con = (struct client_con *)privdata; - write(event_get_fd(con->event), "SENDSTATUS\n", 11); - return 0; -} - -static int data_cb(int fd, void *privdata) -{ - struct client_con *con = (struct client_con *)privdata; - - if (linebuffer_read(con->lbuf, fd) < 0) { - log_print(LOG_WARN, "data_cb(): client %s read", get_sockaddr_buf(&con->addr)); - list_del(&con->list); - free_client(con); - return -1; - } - - char *line; - while ((line = linebuffer_get(con->lbuf)) != NULL) { -// log_print(LOG_DEBUG, "%s: %s", get_sockaddr_buf(&con->addr), line); - - if (strncmp(line, "CTBW ", 5) == 0) { - int bwup, bwdn, liup, lidn; - if (sscanf(line +5, "%d,%d %d,%d", &bwdn, &bwup, &lidn, &liup) == 4) { - con->bw_up = bwup; - con->bw_dn = bwdn; - } - - } 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) { - - con->total_up = totup; - con->total_dn = totdn; - con->chunk_have = chunk1; - con->chunk_total = chunk2; - con->chunk_avail = chunk3; - } - - log_print(LOG_INFO, "%s: got %3.2lf%% of %3.2lf%% [down: %llu (%d), up: %llu (%d)] ", - get_sockaddr_buf(&con->addr), - (double)con->chunk_have / (double)con->chunk_total * 100.0, - (double)con->chunk_avail / (double)con->chunk_total * 100.0, - con->total_dn, con->bw_dn, con->total_up, con->bw_up); - } - - free(line); - } - return 0; -} - -static int accept_cb(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; - } - - unsigned int i = sizeof(con->addr); - int sockfd = accept(fd, (struct sockaddr *)&con->addr, &i); - if (sockfd < 0) { - log_print(LOG_WARN, "accept_cb(): accept()"); - free(con->lbuf); - free(con); - return 0; - } - - log_print(LOG_INFO, "new client %s", get_sockaddr_buf(&con->addr)); - - struct timeval tv = { .tv_sec = 10, .tv_usec = 0 }; - con->trigger = event_add_timeout(&tv, trigger_status, con); - - con->event = event_add_readfd(NULL, sockfd, data_cb, con); - - list_add(&con->list, &client_list); - return 0; -} - static int listen_cb(const char *parameter, void *privdata) { struct sockaddr_in addr; @@ -223,7 +27,7 @@ static int listen_cb(const char *parameter, void *privdata) } log_print(LOG_INFO, "listen on %s", get_sockaddr_buf(&addr)); - event_add_readfd(NULL, sockfd, accept_cb, NULL); + event_add_readfd(NULL, sockfd, privdata, NULL); return 0; } @@ -232,7 +36,10 @@ int main(int argc, char *argv[]) if (config_parse("torrent-stats.conf") < 0) return 1; - config_get_strings("global", "listen", listen_cb, NULL); + config_get_strings("global", "listen", listen_cb, ctcs_accept_handler); + config_get_strings("global", "listen-http", listen_cb, httpd_accept_handler); + + httpd_add_cb("/", 1, ctcs_httpd_handler, NULL); event_loop(); return 0; diff --git a/torrent-stats.conf b/torrent-stats.conf index 6058fa8..175af47 100644 --- a/torrent-stats.conf +++ b/torrent-stats.conf @@ -1,2 +1,3 @@ [global] listen 0.0.0.0:2780 +listen-http 0.0.0.0:8080