summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortiptorrent development team <tiptorrent@soleta.eu>2021-08-17 00:05:31 +0200
committertiptorrent development team <tiptorrent@soleta.eu>2021-09-29 15:47:43 +0200
commit2610239d62d744294e55d44e46937bd6dea87559 (patch)
treea14c9da8d4156e31cd311b036287e7aa9c90a40f
initial commit
-rw-r--r--.gitignore64
-rw-r--r--Makefile.am7
-rw-r--r--README21
-rw-r--r--configure.ac23
-rw-r--r--src/core.c440
-rw-r--r--src/core.h83
-rw-r--r--src/handler.c155
-rw-r--r--src/list.h162
-rw-r--r--src/main.c95
-rwxr-xr-xtests/network-setup.sh84
-rwxr-xr-xtests/run-clients.sh18
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
diff --git a/README b/README
new file mode 100644
index 0000000..91b9f2b
--- /dev/null
+++ b/README
@@ -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 &