diff options
author | tiptorrent development team <tiptorrent@soleta.eu> | 2021-08-17 00:05:31 +0200 |
---|---|---|
committer | tiptorrent development team <tiptorrent@soleta.eu> | 2021-09-29 15:47:43 +0200 |
commit | 2610239d62d744294e55d44e46937bd6dea87559 (patch) | |
tree | a14c9da8d4156e31cd311b036287e7aa9c90a40f |
initial commit
-rw-r--r-- | .gitignore | 64 | ||||
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | README | 21 | ||||
-rw-r--r-- | configure.ac | 23 | ||||
-rw-r--r-- | src/core.c | 440 | ||||
-rw-r--r-- | src/core.h | 83 | ||||
-rw-r--r-- | src/handler.c | 155 | ||||
-rw-r--r-- | src/list.h | 162 | ||||
-rw-r--r-- | src/main.c | 95 | ||||
-rwxr-xr-x | tests/network-setup.sh | 84 | ||||
-rwxr-xr-x | tests/run-clients.sh | 18 |
11 files changed, 1152 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ffbbf4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# http://www.gnu.org/software/automake + +Makefile.in +.deps/ +.dirstamp + +# http://www.gnu.org/software/autoconf + +autom4te.cache +/autoscan.log +/autoscan-*.log +/aclocal.m4 +/compile +/config.cache +/config.guess +/config.h.in +/config.log +/config.status +/config.sub +/configure +/configure~ +/configure.scan +/depcomp +/install-sh +/missing +/stamp-h1 +/build-aux/ + +# https://www.gnu.org/software/libtool/ + +/ltmain.sh + +# http://www.gnu.org/software/texinfo + +/texinfo.tex + +# http://www.gnu.org/software/m4/ + +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 + +# Generated Makefile +# (meta build system like autotools, +# can automatically generate from config.status script +# (which is called by configure script)) +Makefile + +# Object files +*.o +*.ko +*.obj +*.elf + +# Executables +tiptorrent + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..2d76b49 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,7 @@ +sbin_PROGRAMS = tiptorrent + +AM_CFLAGS = ${LIBEVENT_CFLAGS} -g -Wall + +tiptorrent_SOURCES = src/handler.c \ + src/core.c \ + src/main.c @@ -0,0 +1,21 @@ +1) Compile: + + make + +2) Run (serving files in the local folder): + + ./tiptorrent + +3) Generate test file + + dd if=/dev/random of=TEST + ... ctrl-c whenever you like ... + md5sum TEST + +4) Test (on a different terminal) + + wget http://localhost:9999/TEST + + Now check that downloaded file is the same: + + md5sum TEST diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..7764022 --- /dev/null +++ b/configure.ac @@ -0,0 +1,23 @@ +AC_INIT(tiptorrent, 1.0, info@soleta.eu) +AC_CONFIG_AUX_DIR([build-aux]) +AC_PREFIX_DEFAULT(/usr) + +AC_CANONICAL_HOST +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([-Wall foreign subdir-objects tar-pax no-dist-gzip dist-bzip2 1.6]) + +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S + +case "$host" in +*-*-linux*) ;; +*) AC_MSG_ERROR([Linux only, sorry!]);; +esac + +AC_CHECK_LIB([ev], [ev_loop_new], , AC_MSG_ERROR([libev not found])) + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/src/core.c b/src/core.c new file mode 100644 index 0000000..2b64347 --- /dev/null +++ b/src/core.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu> + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include "core.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <ifaddrs.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/tcp.h> +#include <fcntl.h> +#include <time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> + +struct ev_loop *tip_main_loop; + +int num_clients; +static LIST_HEAD(client_list); +static LIST_HEAD(client_redirect_list); + +static void tip_client_activate_pending(void); + +static void tip_client_release(struct ev_loop *loop, struct tip_client *cli) +{ + syslog(LOG_INFO, "closing connection with %s:%hu", + inet_ntoa(cli->addr.sin_addr), htons(cli->addr.sin_port)); + + list_del(&cli->list); + ev_io_stop(loop, &cli->io); + close(cli->io.fd); + if (cli->fd > 0) + close(cli->fd); + + free((void *)cli->uri); + free((void *)cli->path); + free(cli); + num_clients--; + + tip_client_activate_pending(); +} + +static int tip_client_payload_too_large(struct tip_client *cli) +{ + char buf[] = "HTTP/1.1 413 Payload Too Large\r\n" + "Content-Length: 0\r\n\r\n"; + + send(tip_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +static int tip_client_state_recv_hdr(struct tip_client *cli) +{ + char *ptr; + + ptr = strstr(cli->buf, "\r\n\r\n"); + if (!ptr) + return 0; + + cli->msg_len = ptr - cli->buf + 4; + + ptr = strstr(cli->buf, "Content-Length: "); + if (ptr) { + sscanf(ptr, "Content-Length: %i[^\r\n]", &cli->content_length); + if (cli->content_length < 0) + return -1; + cli->msg_len += cli->content_length; + } + + ptr = strstr(cli->buf, "Authorization: "); + if (ptr) + sscanf(ptr, "Authorization: %63[^\r\n]", cli->auth_token); + + return 1; +} + +static int tip_client_recv(struct tip_client *cli, int events) +{ + struct ev_io *io = &cli->io; + int ret; + + ret = recv(io->fd, cli->buf + cli->buf_len, + sizeof(cli->buf) - cli->buf_len, 0); + if (ret <= 0) { + if (ret < 0) { + syslog(LOG_ERR, "error reading from client %s:%hu (%s)\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), + strerror(errno)); + } + return ret; + } + + return ret; +} + +static void tip_client_read_cb(struct ev_loop *loop, struct ev_io *io, int events) +{ + struct tip_client *cli; + int ret; + + cli = container_of(io, struct tip_client, io); + + ret = tip_client_recv(cli, events); + if (ret <= 0) + goto close; + + ev_timer_again(loop, &cli->timer); + + cli->buf_len += ret; + if (cli->buf_len >= sizeof(cli->buf)) { + syslog(LOG_ERR, "client request from %s:%hu is too long\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port)); + tip_client_payload_too_large(cli); + goto close; + } + + switch (cli->state) { + case TIP_CLIENT_RECEIVING_HEADER: + ret = tip_client_state_recv_hdr(cli); + if (ret < 0) + goto close; + if (!ret) + return; + + cli->state = TIP_CLIENT_RECEIVING_PAYLOAD; + /* Fall through. */ + case TIP_CLIENT_RECEIVING_PAYLOAD: + /* Still not enough data to process request. */ + if (cli->buf_len < cli->msg_len) + return; + + cli->state = TIP_CLIENT_PROCESSING_REQUEST; + /* fall through. */ + case TIP_CLIENT_PROCESSING_REQUEST: + ret = tip_client_state_process_payload(cli); + if (ret > 0) { + /* client is pending. */ + return; + } else if (ret < 0) { + syslog(LOG_ERR, "Failed to process HTTP request from %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + goto close; + } + + ev_io_stop(loop, &cli->io); + ev_io_set(&cli->io, tip_client_socket(cli), EV_READ | EV_WRITE); + ev_io_start(loop, &cli->io); + break; + default: + syslog(LOG_ERR, "unknown read state, critical internal error\n"); + goto close; + } + return; +close: + ev_timer_stop(loop, &cli->timer); + tip_client_release(loop, cli); +} + +static void tip_client_redirect_timer_cb(struct ev_loop *loop, ev_timer *timer, + int events) +{ + struct tip_client_redirect *redir; + + redir = container_of(timer, struct tip_client_redirect, timer); + + syslog(LOG_ERR, "timeout for client redirection to %s:%hu for %s\n", + inet_ntoa(redir->addr.sin_addr), ntohs(redir->addr.sin_port), + redir->uri); + + list_del(&redir->list); + free((void *)redir->uri); + free(redir); +} + +static int tip_client_redirect_create(const struct tip_client *cli) +{ + struct tip_client_redirect *redir; + bool found = false; + + if (!redirect || !cli->allow_redirect) + return 0; + + list_for_each_entry(redir, &client_redirect_list, list) { + if (!strcmp(redir->uri, cli->uri) && + redir->addr.sin_addr.s_addr == cli->addr.sin_addr.s_addr) { + found = true; + break; + } + } + + if (found) { + syslog(LOG_INFO, "client redirection to %s:%hu for %s already exists, skipping", + inet_ntoa(cli->addr.sin_addr), htons(cli->addr.sin_port), + cli->uri); + return 0; + } + + redir = calloc(1, sizeof(struct tip_client_redirect)); + if (!redir) + return -1; + + redir->addr = cli->addr; + redir->addr.sin_port = htons(9999); + redir->uri = strdup(cli->uri); + list_add_tail(&redir->list, &client_redirect_list); + + ev_timer_init(&redir->timer, tip_client_redirect_timer_cb, 60, 0.); + ev_timer_start(tip_main_loop, &redir->timer); + + syslog(LOG_INFO, "adding client redirection to %s:%hu for %s", + inet_ntoa(redir->addr.sin_addr), htons(redir->addr.sin_port), + redir->uri); + + return 0; +} + +static void tip_client_write_cb(struct ev_loop *loop, struct ev_io *io, int events) +{ + struct tip_client *cli; + int ret; + + cli = container_of(io, struct tip_client, io); + + ev_timer_again(loop, &cli->timer); + + switch (cli->state) { + case TIP_CLIENT_PROCESSING_REQUEST_2: + ret = tip_client_state_process_payload_reply(cli); + if (ret > 0) { + goto close; + } else if (ret < 0) { + syslog(LOG_ERR, "Failed to process HTTP request from %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + goto close; + } + break; + case TIP_CLIENT_PROCESSING_REQUEST_3: + ret = tip_client_state_process_payload_bulk(cli); + if (ret > 0) + goto shutdown; + else if (ret < 0) { + syslog(LOG_ERR, "Failed to process HTTP request from %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + goto close; + } + break; + default: + syslog(LOG_ERR, "unknown write state, critical internal error\n"); + goto close; + } + return; +shutdown: + if (cli->size > FILE_SIZE_THRESHOLD) + tip_client_redirect_create(cli); +close: + ev_timer_stop(loop, &cli->timer); + tip_client_release(loop, cli); +} + +static void tip_client_cb(struct ev_loop *loop, struct ev_io *io, int events) +{ + if (events & EV_READ) + return tip_client_read_cb(loop, io, events); + if (events & EV_WRITE) + return tip_client_write_cb(loop, io, events); +} + +static void tip_client_timer_cb(struct ev_loop *loop, ev_timer *timer, int events) +{ + struct tip_client *cli; + + cli = container_of(timer, struct tip_client, timer); + + syslog(LOG_ERR, "timeout request for client %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port)); + + tip_client_release(loop, cli); +} + +/* Shut down connection if there is no data after 15 seconds. */ +#define TIP_CLIENT_TIMEOUT 15 + +static void tip_client_start(struct tip_client *cli) +{ + cli->state = TIP_CLIENT_RECEIVING_HEADER; + ev_io_start(tip_main_loop, &cli->io); + ev_timer_init(&cli->timer, tip_client_timer_cb, TIP_CLIENT_TIMEOUT, 0.); + ev_timer_start(tip_main_loop, &cli->timer); +} + +void tip_client_pending(struct tip_client *cli) +{ + ev_io_stop(tip_main_loop, &cli->io); + ev_timer_stop(tip_main_loop, &cli->timer); + cli->state = TIP_CLIENT_PENDING; +} + +static void tip_client_activate_pending(void) +{ + struct tip_client *cli, *next; + + list_for_each_entry_safe(cli, next, &client_list, list) { + if (cli->state != TIP_CLIENT_PENDING) + continue; + + tip_client_redirect(cli); + + ev_io_set(&cli->io, tip_client_socket(cli), EV_READ | EV_WRITE); + ev_io_start(tip_main_loop, &cli->io); + ev_timer_start(tip_main_loop, &cli->timer); + cli->state = TIP_CLIENT_PROCESSING_REQUEST_2; + break; + } +} + +bool tip_client_redirect(struct tip_client *cli) +{ + struct tip_client_redirect *redir, *next; + char addr[INET_ADDRSTRLEN + 1]; + + if (!redirect) + return false; + + inet_ntop(AF_INET, &cli->addr.sin_addr, addr, INET_ADDRSTRLEN); + + list_for_each_entry_safe(redir, next, &client_redirect_list, list) { + if (strcmp(redir->uri, cli->uri) || + redir->addr.sin_addr.s_addr == cli->addr.sin_addr.s_addr) + continue; + + cli->redirect = true; + cli->redirect_addr = redir->addr; + + syslog(LOG_INFO, "redirecting client %s:%hu to %s:%hu", + addr, htons(cli->addr.sin_port), + inet_ntoa(redir->addr.sin_addr), htons(redir->addr.sin_port)); + + free((void *)redir->uri); + ev_timer_stop(tip_main_loop, &redir->timer); + list_del(&redir->list); + free(redir); + + return true; + } + syslog(LOG_INFO, "no client redirections are available for %s:%hu", + addr, htons(cli->addr.sin_port)); + + return false; +} + +#define TIP_TCP_KEEPALIVE_IDLE 60 +#define TIP_TCP_KEEPALIVE_INTL 30 +#define TIP_TCP_KEEPALIVE_CNT 4 + +void tip_server_accept_cb(struct ev_loop *loop, struct ev_io *io, int events) +{ + int intl = TIP_TCP_KEEPALIVE_INTL, cnt = TIP_TCP_KEEPALIVE_CNT; + int on = 1, idle = TIP_TCP_KEEPALIVE_IDLE; + struct sockaddr_in client_addr; + socklen_t addrlen = sizeof(client_addr); + struct tip_client *cli; + int client_sd, flags; + + if (events & EV_ERROR) + return; + + client_sd = accept(io->fd, (struct sockaddr *)&client_addr, &addrlen); + if (client_sd < 0) { + syslog(LOG_ERR, "cannot accept client connection\n"); + return; + } + + flags = fcntl(client_sd, F_GETFL); + fcntl(client_sd, F_SETFL, flags | O_NONBLOCK); + + setsockopt(client_sd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(int)); + setsockopt(client_sd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(int)); + setsockopt(client_sd, IPPROTO_TCP, TCP_KEEPINTVL, &intl, sizeof(int)); + setsockopt(client_sd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(int)); + + cli = (struct tip_client *)calloc(1, sizeof(struct tip_client)); + if (!cli) { + close(client_sd); + return; + } + memcpy(&cli->addr, &client_addr, sizeof(client_addr)); + cli->fd = -1; + + syslog(LOG_ERR, "accepting client connection from %s:%hu", + inet_ntoa(cli->addr.sin_addr), htons(cli->addr.sin_port)); + + list_add_tail(&cli->list, &client_list); + ev_io_init(&cli->io, tip_client_cb, client_sd, EV_READ); + + tip_client_start(cli); +} + +int tip_socket_server_init(const char *port) +{ + struct sockaddr_in local; + int sd, on = 1; + + sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sd < 0) { + syslog(LOG_ERR, "cannot create main socket\n"); + return -1; + } + setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int)); + + local.sin_addr.s_addr = htonl(INADDR_ANY); + local.sin_family = AF_INET; + local.sin_port = htons(atoi(port)); + + if (bind(sd, (struct sockaddr *) &local, sizeof(local)) < 0) { + close(sd); + syslog(LOG_ERR, "cannot bind socket\n"); + return -1; + } + + listen(sd, 250); + + return sd; +} diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..524dd62 --- /dev/null +++ b/src/core.h @@ -0,0 +1,83 @@ +#ifndef _TIP_CORE_H +#define _TIP_CORE_H + +#include <ev.h> +#include "list.h" +#include <stdbool.h> +#include <netinet/in.h> + +#define TIP_MSG_REQUEST_MAXLEN 131072 + +extern const char *root; +#define DEFAULT_MAX_CLIENTS 3 +extern int max_clients; +extern int num_clients; +extern bool redirect; +/* max_client logic only applies for files larger than 1024 bytes. */ +#define FILE_SIZE_THRESHOLD 1024 + +enum tip_client_state { + TIP_CLIENT_PENDING = 0, + TIP_CLIENT_RECEIVING_HEADER, + TIP_CLIENT_RECEIVING_PAYLOAD, + TIP_CLIENT_PROCESSING_REQUEST, + TIP_CLIENT_PROCESSING_REQUEST_2, + TIP_CLIENT_PROCESSING_REQUEST_3, +}; + +struct tip_client { + struct list_head list; + struct ev_io io; + struct ev_timer timer; + struct sockaddr_in addr; + enum tip_client_state state; + char buf[TIP_MSG_REQUEST_MAXLEN]; + unsigned int buf_len; + unsigned int msg_len; + int content_length; + char auth_token[64]; + + /* for file serving. */ + const char *uri; + const char *path; + size_t size; + int fd; + off_t offset; + + /* for redirection. */ + bool redirect; + struct sockaddr_in redirect_addr; + bool allow_redirect; +}; + +static inline int tip_client_socket(const struct tip_client *cli) +{ + return cli->io.fd; +} + +void tip_client_pending(struct tip_client *cli); +bool tip_client_redirect(struct tip_client *cli); + +extern struct ev_loop *tip_main_loop; + +int tip_socket_server_init(const char *port); +void tip_server_accept_cb(struct ev_loop *loop, struct ev_io *io, int events); + +int tip_client_state_process_payload(struct tip_client *cli); +int tip_client_state_process_payload_reply(struct tip_client *cli); +int tip_client_state_process_payload_bulk(struct tip_client *cli); + +enum tip_http_method { + TIP_METHOD_GET = 0, + TIP_METHOD_POST, + TIP_METHOD_NO_HTTP +}; + +struct tip_client_redirect { + struct list_head list; + struct sockaddr_in addr; + const char *uri; + struct ev_timer timer; +}; + +#endif diff --git a/src/handler.c b/src/handler.c new file mode 100644 index 0000000..9bcec71 --- /dev/null +++ b/src/handler.c @@ -0,0 +1,155 @@ +#include "core.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <ifaddrs.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/tcp.h> +#include <fcntl.h> +#include <time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/sendfile.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <limits.h> +#include <errno.h> + +static int tip_client_method_not_found(struct tip_client *cli) +{ + /* To meet RFC 7231, this function MUST generate an Allow header field + * containing the correct methods. For example: "Allow: POST\r\n" + */ + char buf[] = "HTTP/1.1 405 Method Not Allowed\r\n" + "Content-Length: 0\r\n\r\n"; + + send(tip_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +static int tip_client_file_not_found(struct tip_client *cli) +{ + char buf[] = "HTTP/1.1 404 Not Found\r\n" + "Content-Length: 0\r\n\r\n"; + + send(tip_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +/* TODO: sanitize uri, don't escape directory serving files. */ +static bool sanitize(const char *uri) +{ + return true; +} + +#define BLOCK 1024000 + +int tip_client_state_process_payload(struct tip_client *cli) +{ + const char *trailer, *x_redirect; + enum tip_http_method method; + bool allow_redirect = true; + char _uri[32], *uri = _uri; + char path[PATH_MAX + 1]; + char redirect[5]; + struct stat st; + int err; + +/* syslog(LOG_DEBUG, "%s:rhu %.32s ...\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port), cli->buf); */ + + if (!strncmp(cli->buf, "GET", strlen("GET"))) { + method = TIP_METHOD_GET; + if (sscanf(cli->buf, "GET %31s HTTP/1.1", uri) != 1) + return tip_client_method_not_found(cli); + } else { + return tip_client_method_not_found(cli); + } + + x_redirect = strstr(cli->buf, "X-Accept-Redirect: "); + if (x_redirect && + sscanf(x_redirect, "X-Accept-Redirect: %4s", redirect) == 1 && + !strncmp(redirect, "off", strlen("off"))) + allow_redirect = false; + + trailer = strstr(cli->buf, "\r\n\r\n"); + + if (!sanitize(uri)) + return tip_client_method_not_found(cli); + + snprintf(path, PATH_MAX, "%s/%s", root, uri); + + err = stat(path, &st); + if (err < 0) + return tip_client_file_not_found(cli); + + /* skip initial / */ + uri++; + + cli->uri = strdup(uri); + cli->path = strdup(path); + cli->size = st.st_size; + cli->allow_redirect = allow_redirect; + + num_clients++; + if (cli->size > FILE_SIZE_THRESHOLD && num_clients > max_clients) { + if (!tip_client_redirect(cli)) { + tip_client_pending(cli); + return 1; + } + } + + cli->state = TIP_CLIENT_PROCESSING_REQUEST_2; + + return 0; +} + +int tip_client_state_process_payload_reply(struct tip_client *cli) +{ + char buf[1024]; + int fd; + + if (cli->redirect) { + snprintf(buf, sizeof(buf), + "HTTP/1.1 301 Moves Permanently\r\nLocation: http://%s:%hu/%s\r\n\r\n", + inet_ntoa(cli->redirect_addr.sin_addr), + htons(cli->redirect_addr.sin_port), cli->uri); + send(tip_client_socket(cli), buf, strlen(buf), 0); + + return 1; + } + + fd = open(cli->path, O_RDONLY); + if (fd < 0) + return tip_client_file_not_found(cli); + + snprintf(buf, sizeof(buf), + "HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n\r\n", + cli->size); + + send(tip_client_socket(cli), buf, strlen(buf), 0); + + cli->fd = fd; + cli->state = TIP_CLIENT_PROCESSING_REQUEST_3; + + return 0; +} + +int tip_client_state_process_payload_bulk(struct tip_client *cli) +{ + sendfile(tip_client_socket(cli), cli->fd, &cli->offset, BLOCK); + + if (cli->offset >= cli->size) + return 1; + + return 0; +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..8dfd476 --- /dev/null +++ b/src/list.h @@ -0,0 +1,162 @@ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +#include <stddef.h> + +#define container_of(ptr, type, member) ({ \ + typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_head within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..fd543a2 --- /dev/null +++ b/src/main.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu> + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include "core.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <ifaddrs.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/tcp.h> +#include <fcntl.h> +#include <time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <getopt.h> + +int max_clients = DEFAULT_MAX_CLIENTS; +const char *root = "."; +bool redirect; + +static struct option tip_repo_opts[] = { + { "max-clients", 1, 0, 'n' }, + { "redirect", 0, 0, 'r' }, + { "root", 1, 0, 't' }, + { NULL }, +}; + +struct ev_io ev_io_server_rest; + +int main(int argc, char *argv[]) +{ + int socket_rest, val; + + openlog("tiptorrent", LOG_PID, LOG_DAEMON); + + tip_main_loop = ev_default_loop(0); + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + exit(EXIT_FAILURE); + + while (1) { + val = getopt_long(argc, argv, "n:r", tip_repo_opts, NULL); + if (val < 0) + break; + + switch (val) { + case 'n': + max_clients = atoi(optarg); + if (max_clients <= 0) { + syslog(LOG_ERR, "Invalid number for max_clients"); + return EXIT_FAILURE; + } + break; + case 'r': + redirect = true; + break; + case 't': + root = strdup(optarg); + break; + case '?': + return EXIT_FAILURE; + default: + break; + } + } + + socket_rest = tip_socket_server_init("9999"); + if (socket_rest < 0) { + syslog(LOG_ERR, "Cannot open tiptorrent server socket\n"); + exit(EXIT_FAILURE); + } + + ev_io_init(&ev_io_server_rest, tip_server_accept_cb, socket_rest, EV_READ); + ev_io_start(tip_main_loop, &ev_io_server_rest); + + syslog(LOG_INFO, "Waiting for connections\n"); + + while (1) + ev_loop(tip_main_loop, 0); + + exit(EXIT_SUCCESS); +} diff --git a/tests/network-setup.sh b/tests/network-setup.sh new file mode 100755 index 0000000..c8f16db --- /dev/null +++ b/tests/network-setup.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +if [ $UID -ne 0 ] +then + echo "You must be root to run this test script" + exit 0 +fi + +# / c1 +# /- c2 +# srv ----- br -- c3 +# \- c4 + +start () { + ip netns add srv + ip netns add br + ip netns add c1 + ip netns add c2 + ip netns add c3 + ip netns add c4 + + ip link add veth0 netns srv type veth peer name veth0 netns br + ip link add veth1 netns br type veth peer name veth0 netns c1 + ip link add veth2 netns br type veth peer name veth0 netns c2 + ip link add veth3 netns br type veth peer name veth0 netns c3 + ip link add veth4 netns br type veth peer name veth0 netns c4 + + ip -net br link set up dev veth0 + ip -net br link set up dev veth1 + ip -net br link set up dev veth2 + ip -net br link set up dev veth3 + ip -net br link set up dev veth4 + ip -net br link add name br0 type bridge + ip -net br link set dev veth0 master br0 + ip -net br link set dev veth1 master br0 + ip -net br link set dev veth2 master br0 + ip -net br link set dev veth3 master br0 + ip -net br link set dev veth4 master br0 + ip -net br link set up dev br0 + + ip -net srv addr add 10.141.10.1/24 dev veth0 + ip -net srv link set up dev veth0 + ip netns exec srv .././grepo --max-clients 1 --redirect --root . & + + ip -net c1 addr add 10.141.10.2/24 dev veth0 + ip -net c1 link set up dev veth0 + ip netns exec c1 .././tiptorrent --max-clients 1 & + + ip -net c2 addr add 10.141.10.3/24 dev veth0 + ip -net c2 link set up dev veth0 + ip netns exec c2 .././tiptorrent --max-clients 1 & + + ip -net c3 addr add 10.141.10.4/24 dev veth0 + ip -net c3 link set up dev veth0 + ip netns exec c3 .././tiptorrent --max-clients 1 & + + ip -net c4 addr add 10.141.10.5/24 dev veth0 + ip -net c4 link set up dev veth0 + ip netns exec c4 .././tiptorrent --max-clients 1 & +} + +stop () { + ip netns del srv + ip netns del br + ip netns del c1 + ip netns del c2 + ip netns del c3 + ip netns del c4 + killall -15 tiptorrent +} + +case $1 in +start) + start + ;; +stop) + stop + ;; +*) + echo "$0 [start|stop]" + ;; +esac + +exit 0 diff --git a/tests/run-clients.sh b/tests/run-clients.sh new file mode 100755 index 0000000..3857494 --- /dev/null +++ b/tests/run-clients.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if [ ! -f TEST ] +then + echo "create the TEST first, e.g. dd if=/dev/urandom of=TEST bs=750M count=1 iflag=fullblock" + exit 0 +fi + +if [ $UID -ne 0 ] +then + echo "You must be root to run this test script" + exit 0 +fi + +ip netns exec c1 wget http://10.141.10.1:9999/TEST -O /dev/null & +ip netns exec c2 wget http://10.141.10.1:9999/TEST -O /dev/null & +ip netns exec c3 wget http://10.141.10.1:9999/TEST -O /dev/null & +ip netns exec c4 wget http://10.141.10.1:9999/TEST -O /dev/null & |