diff --git a/CMakeLists.txt b/CMakeLists.txt index 762011fb5..a83b2f254 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ else () build_switch(NCD "build badvpn-ncd" OFF) build_switch(TUNCTL "build badvpn-tunctl" OFF) endif () +build_switch(DOSTEST "build dostest-server and dostest-attacker" OFF) if (BUILD_NCD AND NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux")) message(FATAL_ERROR "NCD is only available on Linux") @@ -296,6 +297,11 @@ if (BUILD_NCD) add_subdirectory(ncd-request) endif () +# dostest +if (BUILD_DOSTEST) + add_subdirectory(dostest) +endif () + message(STATUS "Building components:") # print what we're building and what not diff --git a/blog_channels.txt b/blog_channels.txt index b0e7e29b2..589022827 100644 --- a/blog_channels.txt +++ b/blog_channels.txt @@ -128,3 +128,5 @@ ncd_net_ipv6_addr 4 ncd_net_ipv6_route 4 ncd_net_ipv4_addr_in_network 4 ncd_net_ipv6_addr_in_network 4 +dostest_server 4 +dostest_attacker 4 diff --git a/dostest/CMakeLists.txt b/dostest/CMakeLists.txt new file mode 100644 index 000000000..8d3b74223 --- /dev/null +++ b/dostest/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(dostest-server + dostest-server.c + StreamBuffer.c +) +target_link_libraries(dostest-server base system) + +add_executable(dostest-attacker + dostest-attacker.c +) +target_link_libraries(dostest-attacker base system) diff --git a/dostest/StreamBuffer.c b/dostest/StreamBuffer.c new file mode 100644 index 000000000..d43912767 --- /dev/null +++ b/dostest/StreamBuffer.c @@ -0,0 +1,147 @@ +/** + * @file StreamBuffer.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "StreamBuffer.h" + +// called when receive operation is complete +static void input_handler_done (void *vo, int data_len) +{ + StreamBuffer *o = (StreamBuffer *)vo; + ASSERT(data_len > 0) + ASSERT(data_len <= o->buf_size - (o->buf_start + o->buf_used)) + + // remember if buffer was empty + int was_empty = (o->buf_used == 0); + + // increment buf_used by the amount that was received + o->buf_used += data_len; + + // start another receive operation unless buffer is full + if (o->buf_used < o->buf_size - o->buf_start) { + int end = o->buf_start + o->buf_used; + StreamRecvInterface_Receiver_Recv(o->input, o->buf + end, o->buf_size - end); + } + else if (o->buf_used < o->buf_size) { + // wrap around + StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_start); + } + + // if buffer was empty before, start send operation + if (was_empty) { + StreamPassInterface_Sender_Send(o->output, o->buf + o->buf_start, o->buf_used); + } +} + +// called when send operation is complete +static void output_handler_done (void *vo, int data_len) +{ + StreamBuffer *o = (StreamBuffer *)vo; + ASSERT(data_len > 0) + ASSERT(data_len <= o->buf_used) + ASSERT(data_len <= o->buf_size - o->buf_start) + + // remember if buffer was full + int was_full = (o->buf_used == o->buf_size); + + // increment buf_start and decrement buf_used by the + // amount that was sent + o->buf_start += data_len; + o->buf_used -= data_len; + + // wrap around buf_start + if (o->buf_start == o->buf_size) { + o->buf_start = 0; + } + + // start receive operation if buffer was full + if (was_full) { + int end; + int avail; + if (o->buf_used >= o->buf_size - o->buf_start) { + end = o->buf_used - (o->buf_size - o->buf_start); + avail = o->buf_start - end; + } else { + end = o->buf_start + o->buf_used; + avail = o->buf_size - end; + } + StreamRecvInterface_Receiver_Recv(o->input, o->buf + end, avail); + } + + // start another receive send unless buffer is empty + if (o->buf_used > 0) { + int to_send = bmin_int(o->buf_used, o->buf_size - o->buf_start); + StreamPassInterface_Sender_Send(o->output, o->buf + o->buf_start, to_send); + } +} + +int StreamBuffer_Init (StreamBuffer *o, int buf_size, StreamRecvInterface *input, StreamPassInterface *output) +{ + ASSERT(buf_size > 0) + ASSERT(input) + ASSERT(output) + + // remember arguments + o->buf_size = buf_size; + o->input = input; + o->output = output; + + // allocate buffer memory + o->buf = (uint8_t *)BAllocSize(bsize_fromint(o->buf_size)); + if (!o->buf) { + goto fail0; + } + + // set initial buffer state + o->buf_start = 0; + o->buf_used = 0; + + // set receive and send done callbacks + StreamRecvInterface_Receiver_Init(o->input, input_handler_done, o); + StreamPassInterface_Sender_Init(o->output, output_handler_done, o); + + // start receive operation + StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_size); + + DebugObject_Init(&o->d_obj); + return 1; + +fail0: + return 0; +} + +void StreamBuffer_Free (StreamBuffer *o) +{ + DebugObject_Free(&o->d_obj); + + // free buffer memory + BFree(o->buf); +} diff --git a/dostest/StreamBuffer.h b/dostest/StreamBuffer.h new file mode 100644 index 000000000..dd441f5b4 --- /dev/null +++ b/dostest/StreamBuffer.h @@ -0,0 +1,70 @@ +/** + * @file StreamBuffer.h + * @author Ambroz Bizjak + * + * @section LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BADVPN_STREAMBUFFER_H +#define BADVPN_STREAMBUFFER_H + +#include +#include +#include +#include + +/** + * Buffer object which reads data from a \link StreamRecvInterface and writes + * it to a \link StreamPassInterface. + */ +typedef struct { + int buf_size; + StreamRecvInterface *input; + StreamPassInterface *output; + uint8_t *buf; + int buf_start; + int buf_used; + DebugObject d_obj; +} StreamBuffer; + +/** + * Initializes the buffer object. + * + * @param o object to initialize + * @param buf_size size of the buffer. Must be >0. + * @param input input interface + * @param outout output interface + * @return 1 on success, 0 on failure + */ +int StreamBuffer_Init (StreamBuffer *o, int buf_size, StreamRecvInterface *input, StreamPassInterface *output) WARN_UNUSED; + +/** + * Frees the buffer object. + * + * @param o object to free + */ +void StreamBuffer_Free (StreamBuffer *o); + +#endif diff --git a/dostest/dostest-attacker.c b/dostest/dostest-attacker.c new file mode 100644 index 000000000..723dadd81 --- /dev/null +++ b/dostest/dostest-attacker.c @@ -0,0 +1,512 @@ +/** + * @file dostest-attacker.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PROGRAM_NAME "dostest-attacker" + +// connection structure +struct connection { + int connected; + BConnector connector; + BConnection con; + StreamRecvInterface *recv_if; + uint8_t buf[512]; + LinkedList1Node connections_list_node; +}; + +// command-line options +static struct { + int help; + int version; + char *connect_addr; + int max_connections; + int max_connecting; + int loglevel; + int loglevels[BLOG_NUM_CHANNELS]; +} options; + +// connect address +static BAddr connect_addr; + +// reactor +static BReactor reactor; + +// connections +static LinkedList1 connections_list; +static int num_connections; +static int num_connecting; + +// timer for scheduling creation of more connections +static BTimer make_connections_timer; + +static void print_help (const char *name); +static void print_version (void); +static int parse_arguments (int argc, char *argv[]); +static int process_arguments (void); +static void signal_handler (void *unused); +static int connection_new (void); +static void connection_free (struct connection *conn); +static void connection_logfunc (struct connection *conn); +static void connection_log (struct connection *conn, int level, const char *fmt, ...); +static void connection_connector_handler (struct connection *conn, int is_error); +static void connection_connection_handler (struct connection *conn, int event); +static void connection_recv_handler_done (struct connection *conn, int data_len); +static void make_connections_timer_handler (void *unused); + +int main (int argc, char **argv) +{ + if (argc <= 0) { + return 1; + } + + // open standard streams + open_standard_streams(); + + // parse command-line arguments + if (!parse_arguments(argc, argv)) { + fprintf(stderr, "Failed to parse arguments\n"); + print_help(argv[0]); + goto fail0; + } + + // handle --help and --version + if (options.help) { + print_version(); + print_help(argv[0]); + return 0; + } + if (options.version) { + print_version(); + return 0; + } + + // init loger + BLog_InitStderr(); + + // configure logger channels + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + if (options.loglevels[i] >= 0) { + BLog_SetChannelLoglevel(i, options.loglevels[i]); + } + else if (options.loglevel >= 0) { + BLog_SetChannelLoglevel(i, options.loglevel); + } + } + + BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION); + + // initialize network + if (!BNetwork_GlobalInit()) { + BLog(BLOG_ERROR, "BNetwork_GlobalInit failed"); + goto fail1; + } + + // process arguments + if (!process_arguments()) { + BLog(BLOG_ERROR, "Failed to process arguments"); + goto fail1; + } + + // init time + BTime_Init(); + + // init reactor + if (!BReactor_Init(&reactor)) { + BLog(BLOG_ERROR, "BReactor_Init failed"); + goto fail1; + } + + // setup signal handler + if (!BSignal_Init(&reactor, signal_handler, NULL)) { + BLog(BLOG_ERROR, "BSignal_Init failed"); + goto fail2; + } + + // init connections list + LinkedList1_Init(&connections_list); + num_connections = 0; + num_connecting = 0; + + // init make connections timer + BTimer_Init(&make_connections_timer, 0, make_connections_timer_handler, NULL); + BReactor_SetTimer(&reactor, &make_connections_timer); + + // enter event loop + BLog(BLOG_NOTICE, "entering event loop"); + BReactor_Exec(&reactor); + + // free connections + while (!LinkedList1_IsEmpty(&connections_list)) { + struct connection *conn = UPPER_OBJECT(LinkedList1_GetFirst(&connections_list), struct connection, connections_list_node); + connection_free(conn); + } + // free make connections timer + BReactor_RemoveTimer(&reactor, &make_connections_timer); + // free signal + BSignal_Finish(); +fail2: + // free reactor + BReactor_Free(&reactor); +fail1: + // free logger + BLog(BLOG_NOTICE, "exiting"); + BLog_Free(); +fail0: + // finish debug objects + DebugObjectGlobal_Finish(); + + return 1; +} + +void print_help (const char *name) +{ + printf( + "Usage:\n" + " %s\n" + " [--help]\n" + " [--version]\n" + " --connect-addr \n" + " --max-connections \n" + " --max-connecting \n" + " [--loglevel <0-5/none/error/warning/notice/info/debug>]\n" + " [--channel-loglevel <0-5/none/error/warning/notice/info/debug>] ...\n" + "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n", + name + ); +} + +void print_version (void) +{ + printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n"); +} + +int parse_arguments (int argc, char *argv[]) +{ + options.help = 0; + options.version = 0; + options.connect_addr = NULL; + options.max_connections = -1; + options.max_connecting = -1; + options.loglevel = -1; + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + options.loglevels[i] = -1; + } + + int i; + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "--help")) { + options.help = 1; + } + else if (!strcmp(arg, "--version")) { + options.version = 1; + } + else if (!strcmp(arg, "--connect-addr")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.connect_addr = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--max-connections")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.max_connections = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--max-connecting")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.max_connecting = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--loglevel")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--channel-loglevel")) { + if (2 >= argc - i) { + fprintf(stderr, "%s: requires two arguments\n", arg); + return 0; + } + int channel = BLogGlobal_GetChannelByName(argv[i + 1]); + if (channel < 0) { + fprintf(stderr, "%s: wrong channel argument\n", arg); + return 0; + } + int loglevel = parse_loglevel(argv[i + 2]); + if (loglevel < 0) { + fprintf(stderr, "%s: wrong loglevel argument\n", arg); + return 0; + } + options.loglevels[channel] = loglevel; + i += 2; + } + else { + fprintf(stderr, "unknown option: %s\n", arg); + return 0; + } + } + + if (options.help || options.version) { + return 1; + } + + if (!options.connect_addr) { + fprintf(stderr, "--connect-addr missing\n"); + return 0; + } + + if (options.max_connections == -1) { + fprintf(stderr, "--max-connections missing\n"); + return 0; + } + + if (options.max_connecting == -1) { + fprintf(stderr, "--max-connecting missing\n"); + return 0; + } + + return 1; +} + +int process_arguments (void) +{ + // resolve listen address + if (!BAddr_Parse(&connect_addr, options.connect_addr, NULL, 0)) { + BLog(BLOG_ERROR, "connect addr: BAddr_Parse failed"); + return 0; + } + + return 1; +} + +void signal_handler (void *unused) +{ + BLog(BLOG_NOTICE, "termination requested"); + + // exit event loop + BReactor_Quit(&reactor, 1); +} + +int connection_new (void) +{ + // allocate structure + struct connection *conn = (struct connection *)malloc(sizeof(*conn)); + if (!conn) { + BLog(BLOG_ERROR, "malloc failed"); + goto fail0; + } + + // set not connected + conn->connected = 0; + + // init connector + if (!BConnector_Init(&conn->connector, connect_addr, &reactor, conn, (BConnector_handler)connection_connector_handler)) { + BLog(BLOG_ERROR, "BConnector_Init failed"); + goto fail1; + } + + // add to connections list + LinkedList1_Append(&connections_list, &conn->connections_list_node); + num_connections++; + num_connecting++; + + return 1; + +fail1: + free(conn); +fail0: + return 0; +} + +void connection_free (struct connection *conn) +{ + // remove from connections list + LinkedList1_Remove(&connections_list, &conn->connections_list_node); + num_connections--; + if (!conn->connected) { + num_connecting--; + } + + if (conn->connected) { + // free receive interface + BConnection_RecvAsync_Free(&conn->con); + + // free connection + BConnection_Free(&conn->con); + } + + // free connector + BConnector_Free(&conn->connector); + + // free structure + free(conn); +} + +void connection_logfunc (struct connection *conn) +{ + BLog_Append("%d connection (%p): ", num_connecting, (void *)conn); +} + +void connection_log (struct connection *conn, int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + BLog_LogViaFuncVarArg((BLog_logfunc)connection_logfunc, conn, BLOG_CURRENT_CHANNEL, level, fmt, vl); + va_end(vl); +} + +void connection_connector_handler (struct connection *conn, int is_error) +{ + ASSERT(!conn->connected) + + // check for connection error + if (is_error) { + connection_log(conn, BLOG_INFO, "failed to connect"); + goto fail0; + } + + // init connection from connector + if (!BConnection_Init(&conn->con, BConnection_source_connector(&conn->connector), &reactor, conn, (BConnection_handler)connection_connection_handler)) { + connection_log(conn, BLOG_INFO, "BConnection_Init failed"); + goto fail0; + } + + // init receive interface + BConnection_RecvAsync_Init(&conn->con); + conn->recv_if = BConnection_RecvAsync_GetIf(&conn->con); + StreamRecvInterface_Receiver_Init(conn->recv_if, (StreamRecvInterface_handler_done)connection_recv_handler_done, conn); + + // start receiving + StreamRecvInterface_Receiver_Recv(conn->recv_if, conn->buf, sizeof(conn->buf)); + + // no longer connecting + conn->connected = 1; + num_connecting--; + + connection_log(conn, BLOG_INFO, "connected"); + + // schedule making connections (because of connecting limit) + BReactor_SetTimer(&reactor, &make_connections_timer); + return; + +fail0: + // free connection + connection_free(conn); + + // schedule making connections + BReactor_SetTimer(&reactor, &make_connections_timer); +} + +void connection_connection_handler (struct connection *conn, int event) +{ + ASSERT(conn->connected) + + if (event == BCONNECTION_EVENT_RECVCLOSED) { + connection_log(conn, BLOG_INFO, "connection closed"); + } else { + connection_log(conn, BLOG_INFO, "connection error"); + } + + // free connection + connection_free(conn); + + // schedule making connections + BReactor_SetTimer(&reactor, &make_connections_timer); +} + +void connection_recv_handler_done (struct connection *conn, int data_len) +{ + ASSERT(conn->connected) + + // receive more + StreamRecvInterface_Receiver_Recv(conn->recv_if, conn->buf, sizeof(conn->buf)); + + connection_log(conn, BLOG_INFO, "received %d bytes", data_len); +} + +void make_connections_timer_handler (void *unused) +{ + int make_num = bmin_int(options.max_connections - num_connections, options.max_connecting - num_connecting); + + if (make_num <= 0) { + return; + } + + BLog(BLOG_INFO, "making %d connections", make_num); + + for (int i = 0; i < make_num; i++) { + if (!connection_new()) { + // can happen if fd limit is reached + BLog(BLOG_ERROR, "failed to make connection, waiting"); + BReactor_SetTimerAfter(&reactor, &make_connections_timer, 10); + return; + } + } +} diff --git a/dostest/dostest-server.c b/dostest/dostest-server.c new file mode 100644 index 000000000..744759145 --- /dev/null +++ b/dostest/dostest-server.c @@ -0,0 +1,567 @@ +/** + * @file dostest-server.c + * @author Ambroz Bizjak + * + * @section LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#ifdef BADVPN_LINUX +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "StreamBuffer.h" + +#include + +#define PROGRAM_NAME "dostest-server" + +#ifdef BADVPN_LINUX +#ifndef SO_DOSDEF_PREPARE +#define SO_DOSDEF_PREPARE 48 +#endif +#ifndef SO_DOSDEF_ACTIVATE +#define SO_DOSDEF_ACTIVATE 49 +#endif +#endif + +#define BUF_SIZE 1024 + +// client structure +struct client { + BConnection con; + BAddr addr; + StreamBuffer buf; + BTimer disconnect_timer; + LinkedList1Node clients_list_node; +}; + +// command-line options +static struct { + int help; + int version; + char *listen_addr; + int max_clients; + int disconnect_time; + int defense_prepare_clients; + int defense_activate_clients; + int loglevel; + int loglevels[BLOG_NUM_CHANNELS]; +} options; + +// listen address +static BAddr listen_addr; + +// reactor +static BReactor ss; + +// listener +static BListener listener; + +// clients +static LinkedList1 clients_list; +static int num_clients; + +// defense status +static int defense_prepare; +static int defense_activate; + +static void print_help (const char *name); +static void print_version (void); +static int parse_arguments (int argc, char *argv[]); +static int process_arguments (void); +static void signal_handler (void *unused); +static void listener_handler (void *unused); +static void client_free (struct client *client); +static void client_logfunc (struct client *client); +static void client_log (struct client *client, int level, const char *fmt, ...); +static void client_disconnect_timer_handler (struct client *client); +static void client_connection_handler (struct client *client, int event); +static void update_defense (void); + +int main (int argc, char **argv) +{ + if (argc <= 0) { + return 1; + } + + // open standard streams + open_standard_streams(); + + // parse command-line arguments + if (!parse_arguments(argc, argv)) { + fprintf(stderr, "Failed to parse arguments\n"); + print_help(argv[0]); + goto fail0; + } + + // handle --help and --version + if (options.help) { + print_version(); + print_help(argv[0]); + return 0; + } + if (options.version) { + print_version(); + return 0; + } + + // init loger + BLog_InitStderr(); + + // configure logger channels + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + if (options.loglevels[i] >= 0) { + BLog_SetChannelLoglevel(i, options.loglevels[i]); + } + else if (options.loglevel >= 0) { + BLog_SetChannelLoglevel(i, options.loglevel); + } + } + + BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION); + + // initialize network + if (!BNetwork_GlobalInit()) { + BLog(BLOG_ERROR, "BNetwork_GlobalInit failed"); + goto fail1; + } + + // process arguments + if (!process_arguments()) { + BLog(BLOG_ERROR, "Failed to process arguments"); + goto fail1; + } + + // init time + BTime_Init(); + + // init reactor + if (!BReactor_Init(&ss)) { + BLog(BLOG_ERROR, "BReactor_Init failed"); + goto fail1; + } + + // setup signal handler + if (!BSignal_Init(&ss, signal_handler, NULL)) { + BLog(BLOG_ERROR, "BSignal_Init failed"); + goto fail2; + } + + // initialize listener + if (!BListener_Init(&listener, listen_addr, &ss, NULL, listener_handler)) { + BLog(BLOG_ERROR, "Listener_Init failed"); + goto fail3; + } + + // init clients list + LinkedList1_Init(&clients_list); + num_clients = 0; + + // clear defense state + defense_prepare = 0; + defense_activate = 0; + + // update defense + update_defense(); + + // enter event loop + BLog(BLOG_NOTICE, "entering event loop"); + BReactor_Exec(&ss); + + // free clients + while (!LinkedList1_IsEmpty(&clients_list)) { + struct client *client = UPPER_OBJECT(LinkedList1_GetFirst(&clients_list), struct client, clients_list_node); + client_free(client); + } + // free listener + BListener_Free(&listener); +fail3: + // free signal + BSignal_Finish(); +fail2: + // free reactor + BReactor_Free(&ss); +fail1: + // free logger + BLog(BLOG_NOTICE, "exiting"); + BLog_Free(); +fail0: + // finish debug objects + DebugObjectGlobal_Finish(); + + return 1; +} + +void print_help (const char *name) +{ + printf( + "Usage:\n" + " %s\n" + " [--help]\n" + " [--version]\n" + " --listen-addr \n" + " --max-clients \n" + " --disconnect-time \n" + " [--defense-prepare-clients ]\n" + " [--defense-activate-clients ]\n" + " [--loglevel <0-5/none/error/warning/notice/info/debug>]\n" + " [--channel-loglevel <0-5/none/error/warning/notice/info/debug>] ...\n" + "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n", + name + ); +} + +void print_version (void) +{ + printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n"); +} + +int parse_arguments (int argc, char *argv[]) +{ + options.help = 0; + options.version = 0; + options.listen_addr = NULL; + options.max_clients = -1; + options.disconnect_time = -1; + options.defense_prepare_clients = -1; + options.defense_activate_clients = -1; + options.loglevel = -1; + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + options.loglevels[i] = -1; + } + + int i; + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "--help")) { + options.help = 1; + } + else if (!strcmp(arg, "--version")) { + options.version = 1; + } + else if (!strcmp(arg, "--listen-addr")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.listen_addr = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--max-clients")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.max_clients = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--disconnect-time")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.disconnect_time = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--defense-prepare-clients")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.defense_prepare_clients = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--defense-activate-clients")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.defense_activate_clients = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--loglevel")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--channel-loglevel")) { + if (2 >= argc - i) { + fprintf(stderr, "%s: requires two arguments\n", arg); + return 0; + } + int channel = BLogGlobal_GetChannelByName(argv[i + 1]); + if (channel < 0) { + fprintf(stderr, "%s: wrong channel argument\n", arg); + return 0; + } + int loglevel = parse_loglevel(argv[i + 2]); + if (loglevel < 0) { + fprintf(stderr, "%s: wrong loglevel argument\n", arg); + return 0; + } + options.loglevels[channel] = loglevel; + i += 2; + } + else { + fprintf(stderr, "unknown option: %s\n", arg); + return 0; + } + } + + if (options.help || options.version) { + return 1; + } + + if (!options.listen_addr) { + fprintf(stderr, "--listen-addr missing\n"); + return 0; + } + + if (options.max_clients == -1) { + fprintf(stderr, "--max-clients missing\n"); + return 0; + } + + if (options.disconnect_time == -1) { + fprintf(stderr, "--disconnect-time missing\n"); + return 0; + } + + return 1; +} + +int process_arguments (void) +{ + // resolve listen address + if (!BAddr_Parse(&listen_addr, options.listen_addr, NULL, 0)) { + BLog(BLOG_ERROR, "listen addr: BAddr_Parse failed"); + return 0; + } + + return 1; +} + +void signal_handler (void *unused) +{ + BLog(BLOG_NOTICE, "termination requested"); + + // exit event loop + BReactor_Quit(&ss, 1); +} + +void listener_handler (void *unused) +{ + if (num_clients == options.max_clients) { + BLog(BLOG_ERROR, "maximum number of clients reached"); + goto fail0; + } + + // allocate structure + struct client *client = (struct client *)malloc(sizeof(*client)); + if (!client) { + BLog(BLOG_ERROR, "malloc failed"); + goto fail0; + } + + // accept client + if (!BConnection_Init(&client->con, BConnection_source_listener(&listener, &client->addr), &ss, client, (BConnection_handler)client_connection_handler)) { + BLog(BLOG_ERROR, "BConnection_Init failed"); + goto fail1; + } + + // init connection interfaces + BConnection_RecvAsync_Init(&client->con); + BConnection_SendAsync_Init(&client->con); + StreamRecvInterface *recv_if = BConnection_RecvAsync_GetIf(&client->con); + StreamPassInterface *send_if = BConnection_SendAsync_GetIf(&client->con); + + // init stream buffer (to loop received data back to the client) + if (!StreamBuffer_Init(&client->buf, BUF_SIZE, recv_if, send_if)) { + BLog(BLOG_ERROR, "StreamBuffer_Init failed"); + goto fail2; + } + + // init disconnect timer + BTimer_Init(&client->disconnect_timer, options.disconnect_time, (BTimer_handler)client_disconnect_timer_handler, client); + BReactor_SetTimer(&ss, &client->disconnect_timer); + + // insert to clients list + LinkedList1_Append(&clients_list, &client->clients_list_node); + num_clients++; + + client_log(client, BLOG_INFO, "connected"); + BLog(BLOG_NOTICE, "%d clients", num_clients); + + // update defense + update_defense(); + return; + +fail2: + BConnection_SendAsync_Free(&client->con); + BConnection_RecvAsync_Free(&client->con); + BConnection_Free(&client->con); +fail1: + free(client); +fail0: + return; +} + +void client_free (struct client *client) +{ + // remove from clients list + LinkedList1_Remove(&clients_list, &client->clients_list_node); + num_clients--; + + // free disconnect timer + BReactor_RemoveTimer(&ss, &client->disconnect_timer); + + // free stream buffer + StreamBuffer_Free(&client->buf); + + // free connection interfaces + BConnection_SendAsync_Free(&client->con); + BConnection_RecvAsync_Free(&client->con); + + // free connection + BConnection_Free(&client->con); + + // free structure + free(client); + + BLog(BLOG_NOTICE, "%d clients", num_clients); + + // update defense + update_defense(); +} + +void client_logfunc (struct client *client) +{ + char addr[BADDR_MAX_PRINT_LEN]; + BAddr_Print(&client->addr, addr); + + BLog_Append("client (%s): ", addr); +} + +void client_log (struct client *client, int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + BLog_LogViaFuncVarArg((BLog_logfunc)client_logfunc, client, BLOG_CURRENT_CHANNEL, level, fmt, vl); + va_end(vl); +} + +void client_disconnect_timer_handler (struct client *client) +{ + client_log(client, BLOG_INFO, "timed out, disconnecting"); + + // free client + client_free(client); +} + +void client_connection_handler (struct client *client, int event) +{ + if (event == BCONNECTION_EVENT_RECVCLOSED) { + client_log(client, BLOG_INFO, "client closed"); + } else { + client_log(client, BLOG_INFO, "client error"); + } + + // free client + client_free(client); +} + +void update_defense (void) +{ +#ifdef BADVPN_LINUX + if (options.defense_prepare_clients != -1) { + int val = num_clients >= options.defense_prepare_clients; + int res = setsockopt(listener.fd, SOL_SOCKET, SO_DOSDEF_PREPARE, &val, sizeof(val)); + if (res < 0) { + BLog(BLOG_ERROR, "failed to %s defense preparation", (val ? "enable" : "disable")); + } else { + if (!defense_prepare && val) { + BLog(BLOG_NOTICE, "defense preparation enabled"); + } + else if (defense_prepare && !val) { + BLog(BLOG_NOTICE, "defense preparation disabled"); + } + } + defense_prepare = val; + } + + if (options.defense_activate_clients != -1) { + int val = num_clients >= options.defense_activate_clients; + int res = setsockopt(listener.fd, SOL_SOCKET, SO_DOSDEF_ACTIVATE, &val, sizeof(val)); + if (res < 0) { + BLog(BLOG_ERROR, "failed to %s defense activation", (val ? "enable" : "disable")); + } else { + if (!defense_activate && val) { + BLog(BLOG_NOTICE, "defense activation enabled"); + } + else if (defense_activate && !val) { + BLog(BLOG_NOTICE, "defense activation disabled"); + } + } + defense_activate = val; + } +#endif +} diff --git a/generated/blog_channel_dostest_attacker.h b/generated/blog_channel_dostest_attacker.h new file mode 100644 index 000000000..b267c8f40 --- /dev/null +++ b/generated/blog_channel_dostest_attacker.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_dostest_attacker diff --git a/generated/blog_channel_dostest_server.h b/generated/blog_channel_dostest_server.h new file mode 100644 index 000000000..8d3988e0f --- /dev/null +++ b/generated/blog_channel_dostest_server.h @@ -0,0 +1,4 @@ +#ifdef BLOG_CURRENT_CHANNEL +#undef BLOG_CURRENT_CHANNEL +#endif +#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_dostest_server diff --git a/generated/blog_channels_defines.h b/generated/blog_channels_defines.h index bdba32eaa..0dc2afe6f 100644 --- a/generated/blog_channels_defines.h +++ b/generated/blog_channels_defines.h @@ -128,4 +128,6 @@ #define BLOG_CHANNEL_ncd_net_ipv6_route 127 #define BLOG_CHANNEL_ncd_net_ipv4_addr_in_network 128 #define BLOG_CHANNEL_ncd_net_ipv6_addr_in_network 129 -#define BLOG_NUM_CHANNELS 130 +#define BLOG_CHANNEL_dostest_server 130 +#define BLOG_CHANNEL_dostest_attacker 131 +#define BLOG_NUM_CHANNELS 132 diff --git a/generated/blog_channels_list.h b/generated/blog_channels_list.h index db1a18b4d..f2f5aa6cb 100644 --- a/generated/blog_channels_list.h +++ b/generated/blog_channels_list.h @@ -128,3 +128,5 @@ {"ncd_net_ipv6_route", 4}, {"ncd_net_ipv4_addr_in_network", 4}, {"ncd_net_ipv6_addr_in_network", 4}, +{"dostest_server", 4}, +{"dostest_attacker", 4},