initial commit
commit
2610239d62
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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 &
|
Loading…
Reference in New Issue