summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore64
-rw-r--r--Makefile.am5
-rw-r--r--configure.ac23
-rw-r--r--src/main.c440
-rwxr-xr-xtests/run-test.sh4
5 files changed, 536 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..d252360
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,5 @@
+sbin_PROGRAMS = tiptorrent-client
+
+AM_CFLAGS = ${LIBEVENT_CFLAGS} -g -Wall
+
+tiptorrent_client_SOURCES = src/main.c
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..e78ac0e
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,23 @@
+AC_INIT(tiptorrent-client, 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/main.c b/src/main.c
new file mode 100644
index 0000000..d08336b
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 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 <stdlib.h>
+#include <stdio.h>
+#include <ev.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <syslog.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <sys/time.h>
+#include <limits.h>
+#include <syslog.h>
+
+#define TIP_TORRENT_PORT 9999
+
+/* number of chunks for files. */
+#define MAX_CHUNKS 4
+
+#define container_of(ptr, type, member) ({ \
+ typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+static const char *filename;
+static const char *addr;
+struct ev_loop *tip_main_loop;
+
+enum {
+ TIP_CLIENT_RECEIVING_HEADER,
+ TIP_CLIENT_RECEIVING_PAYLOAD,
+ TIP_CLIENT_NOTIFY_REDIRECT,
+ TIP_CLIENT_DONE,
+};
+
+struct tip_client {
+ ev_io io;
+ struct sockaddr_in addr;
+ char buf[10240000];
+ uint32_t buf_len;
+ uint64_t data_len;
+ uint64_t content_len;
+ int state;
+ int fd;
+ bool error;
+ bool redirected;
+ const char *payload;
+};
+
+static struct tip_client _cli = {
+ .fd = -1,
+};
+
+struct {
+ uint32_t direct_from_server;
+ uint32_t redirects;
+} tip_client_stats;
+
+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) {
+ syslog(LOG_ERR, "error reading from server %s:%hu (%s)\n",
+ inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port),
+ strerror(errno));
+ }
+
+ return ret;
+}
+
+static void tip_client_close(struct tip_client *cli)
+{
+ ev_io_stop(tip_main_loop, &cli->io);
+ shutdown(cli->io.fd, SHUT_RDWR);
+ close(cli->io.fd);
+ cli->buf_len = 0;
+}
+
+static void tip_client_error(struct tip_client *cli)
+{
+ cli->error = true;
+ tip_client_close(cli);
+}
+
+static int tip_client_connect(const char *addr);
+
+static int tip_client_state_recv_hdr(struct tip_client *cli)
+{
+ char *ptr, *trailer, *payload;
+ char redirect_addr[32];
+ uint32_t payload_len;
+ uint32_t header_len;
+ int ret;
+
+ ptr = strstr(cli->buf, "\r\n\r\n");
+ if (!ptr)
+ return 0;
+
+ if (!strncmp(cli->buf, "HTTP/1.1 404 Not Found", strlen("HTTP/1.1 404 Not Found"))) {
+ syslog(LOG_ERR, "server says file `%s' not found\n", filename);
+ return -1;
+ }
+ if (!strncmp(cli->buf, "HTTP/1.1 301 Moves Permanently", strlen("HTTP/1.1 301 Moves Permanently"))) {
+ ptr = strstr(cli->buf, "Location:");
+ if (!ptr)
+ return -1;
+
+ ret = sscanf(ptr, "Location: http://%31s[^\r\n]",
+ redirect_addr);
+ if (ret != 1)
+ return -1;
+
+ ptr = strchr(redirect_addr, ':');
+ if (!ptr)
+ return -1;
+
+ ptr[0] = '\0';
+
+ syslog(LOG_INFO, "Redirected to %s to fetch file %s\n",
+ redirect_addr, filename);
+
+ cli->redirected = true;
+ tip_client_close(cli);
+ tip_client_connect(redirect_addr);
+ cli->state = TIP_CLIENT_RECEIVING_HEADER;
+
+ return 0;
+ }
+
+ trailer = ptr + 4;
+
+ ptr = strstr(cli->buf, "Content-Length: ");
+ if (!ptr)
+ return -1;
+
+ if (sscanf(ptr, "Content-Length: %lu[^\r\n]", &cli->content_len) != 1)
+ return -1;
+ if (cli->content_len < 0)
+ return -1;
+
+ if (cli->content_len == 0) {
+ cli->buf_len = 0;
+ return 1;
+ }
+
+ if (cli->redirected)
+ tip_client_stats.redirects++;
+ else
+ tip_client_stats.direct_from_server++;
+
+ cli->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (cli->fd < 0)
+ return ret;
+
+ header_len = trailer - cli->buf;
+ payload = cli->buf + header_len;
+ payload_len = cli->buf_len - header_len;
+ cli->data_len += cli->buf_len;
+ cli->buf_len = 0;
+
+ if (payload_len > 0) {
+ ret = write(cli->fd, payload, payload_len);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (payload_len >= cli->content_len)
+ return 0;
+
+ return 1;
+}
+
+static int tip_client_state_recv_payload(struct tip_client *cli)
+{
+ int ret;
+
+ cli->data_len += cli->buf_len;
+
+ ret = write(cli->fd, cli->buf, cli->buf_len);
+ if (ret < 0)
+ return ret;
+
+ cli->buf_len = 0;
+
+ if (cli->data_len >= cli->content_len) {
+ if (cli->redirected) {
+ tip_client_close(cli);
+ tip_client_connect(addr);
+ cli->state = TIP_CLIENT_NOTIFY_REDIRECT;
+
+ return 1;
+ }
+ cli->state = TIP_CLIENT_DONE;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int tip_client_state_notify_redirect(struct tip_client *cli)
+{
+ char *ptr;
+
+ ptr = strstr(cli->buf, "\r\n\r\n");
+ if (!ptr)
+ return 0;
+
+ if (strncmp(cli->buf, "HTTP/1.1 200 OK", strlen("HTTP/1.1 200 OK")))
+ return -1;
+
+ ptr = strstr(cli->buf, "Content-Length: ");
+ if (!ptr)
+ return -1;
+
+ if (sscanf(ptr, "Content-Length: %lu[^\r\n]", &cli->content_len) != 1)
+ return -1;
+ if (cli->content_len < 0)
+ return -1;
+
+ if (cli->content_len != 0)
+ return -1;
+
+ cli->state = TIP_CLIENT_DONE;
+
+ return 0;
+}
+
+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 error;
+
+ cli->buf_len += ret;
+
+ switch (cli->state) {
+ case TIP_CLIENT_RECEIVING_HEADER:
+ ret = tip_client_state_recv_hdr(cli);
+ if (ret < 0)
+ goto error;
+ if (!ret)
+ return;
+
+ cli->state = TIP_CLIENT_RECEIVING_PAYLOAD;
+ /* Fall through. */
+ case TIP_CLIENT_RECEIVING_PAYLOAD:
+ ret = tip_client_state_recv_payload(cli);
+ if (ret < 0)
+ goto error;
+ if (ret == 0)
+ goto close;
+ break;
+ case TIP_CLIENT_NOTIFY_REDIRECT:
+ ret = tip_client_state_notify_redirect(cli);
+ if (ret < 0)
+ goto error;
+ if (ret == 0)
+ goto close;
+ break;
+ }
+ return;
+error:
+ tip_client_error(cli);
+ return;
+close:
+ tip_client_close(cli);
+ return;
+}
+
+static void tip_client_connect_cb(struct ev_loop *loop, struct ev_io *io, int events)
+{
+ struct tip_client *cli;
+ char buf[PATH_MAX + 1];
+ int ret, len;
+
+ cli = container_of(io, struct tip_client, io);
+
+ if (events & EV_ERROR)
+ return;
+
+ len = sizeof(cli->addr);
+ ret = connect(cli->io.fd, (struct sockaddr *)&cli->addr, len);
+ if (ret < 0) {
+ if (errno != EINPROGRESS) {
+ perror("connect");
+ tip_client_error(cli);
+ return;
+ }
+ }
+
+ if (cli->state == TIP_CLIENT_NOTIFY_REDIRECT)
+ snprintf(buf, sizeof(buf), "POST /%s HTTP/1.1\r\n\r\n", filename);
+ else
+ snprintf(buf, sizeof(buf), "GET /%s HTTP/1.1\r\n\r\n", filename);
+
+ ret = send(cli->io.fd, buf, strlen(buf), 0);
+ if (ret < 0) {
+ tip_client_error(cli);
+ return;
+ }
+
+ ev_io_stop(tip_main_loop, &cli->io);
+ ev_io_init(&cli->io, tip_client_read_cb, cli->io.fd, EV_READ);
+ ev_io_start(tip_main_loop, &cli->io);
+}
+
+static int tip_client_connect(const char *addr)
+{
+ struct tip_client *cli = &_cli;
+ int remote_fd;
+ int flags;
+ int len;
+ int ret;
+
+ remote_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (remote_fd < 0)
+ return -1;
+
+ flags = fcntl(remote_fd, F_GETFL);
+ flags |= O_NONBLOCK;
+ ret = fcntl(remote_fd, F_SETFL, flags);
+ if (ret < 0)
+ return ret;
+
+ cli->addr.sin_family = AF_INET;
+ cli->addr.sin_addr.s_addr = inet_addr(addr);
+ cli->addr.sin_port = htons(TIP_TORRENT_PORT);
+
+ len = sizeof(cli->addr);
+ ret = connect(remote_fd, (struct sockaddr *)&cli->addr, len);
+ if (ret < 0 && errno != EINPROGRESS) {
+ perror("connect");
+ return ret;
+ }
+
+ ev_io_init(&cli->io, tip_client_connect_cb, remote_fd, EV_WRITE);
+ ev_io_start(tip_main_loop, &cli->io);
+
+ syslog(LOG_INFO, "connecting to %s to fetch file %s\n", addr, filename);
+
+ return 0;
+}
+
+static uint32_t select_file_chunk(bool *file_chunk)
+{
+ struct timeval tv;
+ uint32_t k;
+ int i;
+
+ gettimeofday(&tv, NULL);
+ srand(tv.tv_usec);
+
+ k = rand() % MAX_CHUNKS;
+ for (i = 0; i < MAX_CHUNKS; i++) {
+ if (!file_chunk[k])
+ break;
+
+ k++;
+ if (k == MAX_CHUNKS)
+ k = 0;
+ }
+
+ return k;
+}
+
+static char _filename[PATH_MAX + 1];
+
+int main(int argc, char *argv[])
+{
+ struct timeval tv_start, tv_stop, tv;
+ bool file_chunk[MAX_CHUNKS] = {};
+ int i, k;
+
+ if (argc != 3) {
+ printf("%s [ip] [file]\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ addr = argv[1];
+
+ openlog("tiptorrent-client", LOG_PID, LOG_DAEMON);
+
+ signal(SIGPIPE, SIG_IGN);
+
+ tip_main_loop = ev_default_loop(0);
+
+ gettimeofday(&tv_start, NULL);
+
+ for (i = 0; i < MAX_CHUNKS; i++) {
+ memset(&_cli, 0, sizeof(_cli));
+
+ k = select_file_chunk(file_chunk);
+ snprintf(_filename, sizeof(_filename), "%s.%u", argv[2], k);
+ filename = _filename;
+
+ syslog(LOG_INFO, "Requesting file %s to server\n", filename);
+
+ tip_client_connect(argv[1]);
+ _cli.state = TIP_CLIENT_RECEIVING_HEADER;
+
+ while (_cli.state != TIP_CLIENT_DONE || _cli.error)
+ ev_loop(tip_main_loop, 0);
+
+ file_chunk[k] = true;
+ }
+
+ if (_cli.state == TIP_CLIENT_DONE) {
+ gettimeofday(&tv_stop, NULL);
+ timersub(&tv_stop, &tv_start, &tv);
+ printf("Done in %lus.%lums. "
+ "Direct from server: %u Redirected: %u\n",
+ tv.tv_sec, tv.tv_usec,
+ tip_client_stats.direct_from_server,
+ tip_client_stats.redirects);
+ return EXIT_SUCCESS;
+ }
+
+ printf("Failure, see syslog for details.\n");
+ return EXIT_FAILURE;
+}
diff --git a/tests/run-test.sh b/tests/run-test.sh
new file mode 100755
index 0000000..afda064
--- /dev/null
+++ b/tests/run-test.sh
@@ -0,0 +1,4 @@
+for ((i=1;i<=18;i++))
+do
+ ip netns exec c$i .././tiptorrent-client 10.141.10.1 TEST &
+done