From ca494922f3815077efbcd28da3748df38c8a6961 Mon Sep 17 00:00:00 2001 From: dimitri staessens Date: Wed, 3 Aug 2016 00:20:18 +0200 Subject: tools: Add a ping application Ouoroboros ping application mimics the GNU ping application. do "oping --help" for options. --- src/tools/CMakeLists.txt | 1 + src/tools/oping/CMakeLists.txt | 21 ++++ src/tools/oping/oping.c | 176 +++++++++++++++++++++++++++++++ src/tools/oping/oping_client.c | 234 +++++++++++++++++++++++++++++++++++++++++ src/tools/oping/oping_server.c | 162 ++++++++++++++++++++++++++++ 5 files changed, 594 insertions(+) create mode 100644 src/tools/oping/CMakeLists.txt create mode 100644 src/tools/oping/oping.c create mode 100644 src/tools/oping/oping_client.c create mode 100644 src/tools/oping/oping_server.c (limited to 'src') diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 9f5e6fbe..e8c24557 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(irm) add_subdirectory(echo) add_subdirectory(cbr) +add_subdirectory(oping) diff --git a/src/tools/oping/CMakeLists.txt b/src/tools/oping/CMakeLists.txt new file mode 100644 index 00000000..a8fc7d86 --- /dev/null +++ b/src/tools/oping/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${CMAKE_BINARY_DIR}/include) + +find_library(LIBM_LIBRARIES m) +if(NOT LIBM_LIBRARIES) + message(FATAL_ERROR "libm not found") +endif() + +set(SOURCE_FILES + # Add source files here + oping.c +) + +add_executable(oping ${SOURCE_FILES}) + +target_link_libraries(oping LINK_PUBLIC ${LIBM_LIBRARIES} ouroboros) + +install(TARGETS oping RUNTIME DESTINATION usr/bin) diff --git a/src/tools/oping/oping.c b/src/tools/oping/oping.c new file mode 100644 index 00000000..cf8dc485 --- /dev/null +++ b/src/tools/oping/oping.c @@ -0,0 +1,176 @@ +/* + * Ouroboros - Copyright (C) 2016 + * + * Ouroboros ping application + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define _POSIX_C_SOURCE 199506L + +#include +#include +#include +#include +#include + +#define OPING_BUF_SIZE 1500 + +#define ECHO_REQUEST 0 +#define ECHO_REPLY 1 + +#define OPING_MAX_FLOWS 256 + +struct c { + char * s_apn; + int interval; + int count; + int size; + + /* stats */ + int sent; + int rcvd; + float rtt_min; + float rtt_max; + float rtt_avg; + float rtt_m2; + + /* needs locking */ + struct timespec * times; + pthread_mutex_t lock; + + pthread_t reader_pt; + pthread_t writer_pt; +} client; + +struct s { + struct timespec times[OPING_MAX_FLOWS]; + pthread_mutex_t lock; + + pthread_t cleaner_pt; + pthread_t accept_pt; + pthread_t server_pt; +} server; + +struct oping_msg { + uint32_t type; + uint32_t id; +}; + + +#include "oping_client.c" +#include "oping_server.c" + +static void usage() +{ + printf("Usage: oping [OPTION]...\n" + "Checks liveness between a client and a server\n" + "and reports the Round Trip Time (RTT)\n\n" + " -l, --listen Run in server mode\n" + "\n" + " -c, --count Number of packets (default 1000)\n" + " -i, --interval Interval (ms, default 1000)\n" + " -n, --server-apn Name of the oping server\n" + " -s, --size Payload size (b, default 64)\n" + " --help Display this help text and exit\n"); +} + +int main(int argc, char ** argv) +{ + int ret = -1; + char * rem = NULL; + bool serv = false; + + if (ap_init(argv[0])) { + printf("Failed to init AP.\n"); + exit(EXIT_FAILURE); + } + + argc--; + argv++; + + client.s_apn = NULL; + client.interval = 1000; + client.size = 64; + client.count = 1000; + + while (argc > 0) { + if (strcmp(*argv, "-i") == 0 || + strcmp(*argv, "--interval") == 0) { + client.interval = strtol(*(++argv), &rem, 10); + --argc; + } else if (strcmp(*argv, "-n") == 0 || + strcmp(*argv, "--server_apn") == 0) { + client.s_apn = *(++argv); + --argc; + } else if (strcmp(*argv, "-c") == 0 || + strcmp(*argv, "--count") == 0) { + client.count = strtol(*(++argv), &rem, 10); + --argc; + } else if (strcmp(*argv, "-s") == 0 || + strcmp(*argv, "--size") == 0) { + client.size = strtol(*(++argv), &rem, 10); + --argc; + } else if (strcmp(*argv, "-l") == 0 || + strcmp(*argv, "--listen") == 0) { + serv = true; + } else { + usage(); + exit(EXIT_SUCCESS); + } + argc--; + argv++; + } + + if (serv) { + ret = server_main(); + } else { + if (client.s_apn == NULL) { + printf("No server specified.\n"); + usage(); + exit(EXIT_SUCCESS); + } + if (client.interval > 10000) { + printf("Ping interval truncated to 10s.\n"); + client.interval = 10000; + } + if (client.size > OPING_BUF_SIZE) { + printf("Packet size truncated to 1500 bytes.\n"); + client.size = 1500; + } + + if (client.size < 2) { + printf("Packet size set to 64 bytes.\n"); + client.size = 64; + } + + if (client.count > 1000000) { + printf("Count truncated to 1 million SDUs.\n"); + client.count = 1000000; + } + + ret = client_main(); + } + + ap_fini(); + + if (ret < 0) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); +} diff --git a/src/tools/oping/oping_client.c b/src/tools/oping/oping_client.c new file mode 100644 index 00000000..23a57549 --- /dev/null +++ b/src/tools/oping/oping_client.c @@ -0,0 +1,234 @@ +/* + * Ouroboros - Copyright (C) 2016 + * + * Ouroboros ping application + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#ifdef __FreeBSD__ +#define __XSI_VISIBLE 500 +#endif + +#include +#include +#include +#include +#include +#include + +void shutdown_client(int signo, siginfo_t * info, void * c) +{ + switch(signo) { + case SIGINT: + case SIGTERM: + case SIGHUP: + pthread_cancel(client.reader_pt); + pthread_cancel(client.writer_pt); + default: + return; + } +} + +void * reader(void * o) +{ + struct timespec timeout = {2, 0}; + struct timespec now = {0, 0}; + + struct oping_msg * msg; + char buf[OPING_BUF_SIZE]; + int fd = 0; + int msg_len = 0; + float ms = 0; + float d = 0; + + msg = (struct oping_msg *) buf; + + /* FIXME: use flow timeout option once we have it */ + while(client.rcvd != client.count && + (fd = flow_select(&timeout)) != -ETIMEDOUT) { + flow_cntl(fd, FLOW_F_SETFL, FLOW_O_NONBLOCK); + while (!((msg_len = flow_read(fd, buf, OPING_BUF_SIZE)) < 0)) { + if (msg_len < 0) + continue; + + if (ntohl(msg->type) != ECHO_REPLY) { + printf("Invalid message received (%d).\n", + msg->type); + continue; + } + + if (ntohl(msg->id) >= client.count) { + printf("Invalid id.\n"); + continue; + } + + ++client.rcvd; + + clock_gettime(CLOCK_REALTIME, &now); + + pthread_mutex_lock(&client.lock); + ms = ts_diff_us(&client.times[ntohl(msg->id)], &now) + /1000.0; + pthread_mutex_unlock(&client.lock); + + printf("%d bytes from %s: seq=%d time=%.3f ms\n", + msg_len, + client.s_apn, + ntohl(msg->id), + ms); + + if (ms < client.rtt_min) + client.rtt_min = ms; + if (ms > client.rtt_max) + client.rtt_max = ms; + + d = (ms - client.rtt_avg); + client.rtt_avg += d / (float) client.rcvd; + client.rtt_m2 += d * (ms - client.rtt_avg); + } + } + + return (void *) 0; +} + +void * writer(void * o) +{ + int * fdp = (int *) o; + struct timespec now; + struct timespec wait = {client.interval / 1000, client.interval % 1000}; + struct oping_msg * msg; + char * buf = malloc(client.size); + + if (buf == NULL) + return (void *) -ENOMEM; + + if (fdp == NULL) + return (void *) -EINVAL; + + memset(buf, 0, client.size); + + msg = (struct oping_msg *) buf; + + printf("Pinging %s with %d bytes of data:\n\n", + client.s_apn, client.size); + + while (client.sent < client.count) { + nanosleep(&wait, NULL); + msg->id = htonl(client.sent); + if (flow_write(*fdp, buf, client.size) == -1) { + printf("Failed to send SDU.\n"); + flow_dealloc(*fdp); + return (void *) -1; + } + + clock_gettime(CLOCK_REALTIME, &now); + + pthread_mutex_lock(&client.lock); + client.times[client.sent++] = now; + pthread_mutex_unlock(&client.lock); + } + + return (void *) 0; +} + +int client_main() +{ + struct sigaction sig_act; + + struct timespec tic; + struct timespec toc; + + int fd = flow_alloc(client.s_apn, NULL, NULL); + if (fd < 0) { + printf("Failed to allocate flow.\n"); + return -1; + } + + if (flow_alloc_res(fd)) { + printf("Flow allocation refused.\n"); + flow_dealloc(fd); + return -1; + } + + memset(&sig_act, 0, sizeof sig_act); + sig_act.sa_sigaction = &shutdown_client; + sig_act.sa_flags = 0; + + if (sigaction(SIGINT, &sig_act, NULL) || + sigaction(SIGTERM, &sig_act, NULL) || + sigaction(SIGHUP, &sig_act, NULL) || + sigaction(SIGPIPE, &sig_act, NULL)) { + printf("Failed to install sighandler.\n"); + return -1; + } + + pthread_mutex_init(&client.lock, NULL); + pthread_mutex_lock(&client.lock); + client.sent = 0; + client.rcvd = 0; + client.rtt_min = FLT_MAX; + client.rtt_max = 0; + client.rtt_avg = 0; + client.rtt_m2 = 0; + client.times = malloc(sizeof(struct timespec) * client.count); + if (client.times == NULL) { + pthread_mutex_unlock(&client.lock); + return -ENOMEM; + } + + pthread_mutex_unlock(&client.lock); + + clock_gettime(CLOCK_REALTIME, &tic); + + pthread_create(&client.reader_pt, NULL, reader, NULL); + pthread_create(&client.writer_pt, NULL, writer, &fd); + + pthread_join(client.writer_pt, NULL); + pthread_join(client.reader_pt, NULL); + + clock_gettime(CLOCK_REALTIME, &toc); + + printf("\n"); + printf("--- %s ping statistics ---\n", client.s_apn); + printf("%d SDU's transmitted, ", client.sent); + printf("%d received, ", client.rcvd); + printf("%d%% packet loss, ", 100 - ((100 * client.rcvd) / client.sent)); + printf("time: %.3f ms\n", ts_diff_us(&tic, &toc) / 1000.0); + printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/", + client.rtt_min, + client.rtt_avg, + client.rtt_max); + client.rcvd > 1 ? + printf("%.3f ms\n", + sqrt(client.rtt_m2 / (float) (client.rcvd - 1))) : + printf("Nan ms\n"); + + pthread_mutex_lock(&client.lock); + free(client.times); + pthread_mutex_unlock(&client.lock); + pthread_mutex_destroy(&client.lock); + + flow_dealloc(fd); + + return 0; +} diff --git a/src/tools/oping/oping_server.c b/src/tools/oping/oping_server.c new file mode 100644 index 00000000..eb0b511b --- /dev/null +++ b/src/tools/oping/oping_server.c @@ -0,0 +1,162 @@ +/* + * Ouroboros - Copyright (C) 2016 + * + * Ouroboros ping application + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#ifdef __FreeBSD__ +#define __XSI_VISIBLE 500 +#endif + +#include +#include +#include + +void shutdown_server(int signo, siginfo_t * info, void * c) +{ + switch(signo) { + case SIGINT: + case SIGTERM: + case SIGHUP: + pthread_cancel(server.server_pt); + pthread_cancel(server.accept_pt); + pthread_cancel(server.cleaner_pt); + default: + return; + } +} + +void * cleaner_thread(void * o) +{ + int i = 0; + struct timespec now = {0, 0}; + int deadline_ms = 10000; + + while (true) { + clock_gettime(CLOCK_REALTIME, &now); + pthread_mutex_lock(&server.lock); + for (i = 0; i < OPING_MAX_FLOWS; ++i) + if (ts_diff_ms(&server.times[i], &now) > deadline_ms) + flow_dealloc(i); + + pthread_mutex_unlock(&server.lock); + sleep(1); + } +} + +void * server_thread(void *o) +{ + char buf[OPING_BUF_SIZE]; + int msg_len = 0; + struct oping_msg * msg = (struct oping_msg *) buf; + struct timespec now = {0, 0}; + + while (true) { + + int fd = flow_select(NULL); + while (!((msg_len = flow_read(fd, buf, OPING_BUF_SIZE)) < 0)) { + if (msg_len < 0) + continue; + + if (ntohl(msg->type) != ECHO_REQUEST) { + printf("Invalid message received.\n"); + continue; + } + + clock_gettime(CLOCK_REALTIME, &now); + + pthread_mutex_lock(&server.lock); + server.times[fd] = now; + pthread_mutex_unlock(&server.lock); + + msg->type = htonl((uint32_t) ECHO_REPLY); + + if (flow_write(fd, buf, msg_len) < 0) { + printf("Error writing to flow (fd %d).\n", fd); + flow_dealloc(fd); + } + } + } + + return (void *) 0; +} + +void * accept_thread(void * o) +{ + int fd = 0; + struct timespec now = {0, 0}; + + printf("Ouroboros ping server started.\n"); + + while (true) { + fd = flow_accept(NULL); + if (fd < 0) { + printf("Failed to accept flow.\n"); + break; + } + + printf("New flow %d.\n", fd); + + if (flow_alloc_resp(fd, 0)) { + printf("Failed to give an allocate response.\n"); + flow_dealloc(fd); + continue; + } + + clock_gettime(CLOCK_REALTIME, &now); + + pthread_mutex_lock(&server.lock); + server.times[fd] = now; + pthread_mutex_unlock(&server.lock); + + flow_cntl(fd, FLOW_F_SETFL, FLOW_O_NONBLOCK); + } + + return (void *) 0; +} + +int server_main() +{ + struct sigaction sig_act; + + memset(&sig_act, 0, sizeof sig_act); + sig_act.sa_sigaction = &shutdown_server; + sig_act.sa_flags = 0; + + if (sigaction(SIGINT, &sig_act, NULL) || + sigaction(SIGTERM, &sig_act, NULL) || + sigaction(SIGHUP, &sig_act, NULL) || + sigaction(SIGPIPE, &sig_act, NULL)) { + printf("Failed to install sighandler.\n"); + return -1; + } + + pthread_create(&server.cleaner_pt, NULL, cleaner_thread, NULL); + pthread_create(&server.accept_pt, NULL, accept_thread, NULL); + pthread_create(&server.server_pt, NULL, server_thread, NULL); + + pthread_join(server.server_pt, NULL); + pthread_join(server.accept_pt, NULL); + pthread_join(server.cleaner_pt, NULL); + + return 0; +} -- cgit v1.2.3