From a157d5dbb2412675eaabd323aa1956d595c8090c Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Mon, 8 May 2023 14:23:13 +0200 Subject: [PATCH 01/12] Minimal keepalive settings --- src/networking.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/networking.c b/src/networking.c index 3aa17ce..fa3f6f6 100644 --- a/src/networking.c +++ b/src/networking.c @@ -31,7 +31,9 @@ * THE SOFTWARE. */ +#include #include +#include #include #include #include @@ -85,6 +87,39 @@ static int tcp_start_server_on_addr_family(const char* port, int ai_family) /* The programm can go on. */ }; + /* TODO make optional */ + if (1){ + int yes = 1; /* Set SO_KEEPALIVE to 1(True) */ + if (setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) + { + perror("ERROR: setsocktop SO_KEEPALIVE"); + fprintf(stderr, "WARNING: Keeping connection alive wont be possible. \n"); + /* The programm can go on. */ + }; + + /* TODO change defaults tcp_keepalive_time = 7200, tcp_keepalive_intvl = 75, tcp_keepalive_probes = 9 */ + int keepidle = 60; // 1 minute + if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)) == -1) + { + perror("setsockopt TCP_KEEPIDLE"); + exit(1); + } + + int keepintvl = 10; // 10 seconds + if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)) == -1) + { + perror("setsockopt TCP_KEEPINTVL"); + exit(1); + } + + int keepcnt = 6; // 3 probes + if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)) == -1) + { + perror("setsockopt TCP_KEEPCNT"); + exit(1); + } + } + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; /* Success */ From 166b24ed324dfd95c62c91148405152c37f333f5 Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Mon, 8 May 2023 14:44:43 +0200 Subject: [PATCH 02/12] Move to extra function --- include/networking.h | 2 ++ src/japi.c | 5 ++++ src/networking.c | 67 ++++++++++++++++++++++---------------------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/include/networking.h b/include/networking.h index 4eaca38..b43ef26 100644 --- a/include/networking.h +++ b/include/networking.h @@ -77,6 +77,8 @@ int tcp4_start_server(const char* port); */ int tcp6_start_server(const char* port); +int enable_tcp_keepalive(int socket_file_descriptor); + #ifdef __cplusplus } #endif diff --git a/src/japi.c b/src/japi.c index f5f7be1..21c6cca 100644 --- a/src/japi.c +++ b/src/japi.c @@ -462,6 +462,11 @@ int japi_start_server(japi_context *ctx, const char *port) return -1; } + /* TODO make optional */ + if (enable_tcp_keepalive(server_socket) < 0){ + return -1; + } + int ret; int nfds; fd_set fdrd; diff --git a/src/networking.c b/src/networking.c index fa3f6f6..b31f5a2 100644 --- a/src/networking.c +++ b/src/networking.c @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -87,39 +86,6 @@ static int tcp_start_server_on_addr_family(const char* port, int ai_family) /* The programm can go on. */ }; - /* TODO make optional */ - if (1){ - int yes = 1; /* Set SO_KEEPALIVE to 1(True) */ - if (setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) - { - perror("ERROR: setsocktop SO_KEEPALIVE"); - fprintf(stderr, "WARNING: Keeping connection alive wont be possible. \n"); - /* The programm can go on. */ - }; - - /* TODO change defaults tcp_keepalive_time = 7200, tcp_keepalive_intvl = 75, tcp_keepalive_probes = 9 */ - int keepidle = 60; // 1 minute - if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)) == -1) - { - perror("setsockopt TCP_KEEPIDLE"); - exit(1); - } - - int keepintvl = 10; // 10 seconds - if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)) == -1) - { - perror("setsockopt TCP_KEEPINTVL"); - exit(1); - } - - int keepcnt = 6; // 3 probes - if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)) == -1) - { - perror("setsockopt TCP_KEEPCNT"); - exit(1); - } - } - if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; /* Success */ @@ -151,3 +117,36 @@ int tcp6_start_server(const char* port) return tcp_start_server_on_addr_family(port, AF_INET6); } +int enable_tcp_keepalive(int socket_file_descriptor) +{ + int yes = 1; /* Set SO_KEEPALIVE to 1(True) */ + if (setsockopt(socket_file_descriptor, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) + { + perror("ERROR: setsocktop SO_KEEPALIVE"); + fprintf(stderr, "WARNING: Keeping connection alive wont be possible. \n"); + /* The programm can go on. */ + }; + + /* change defaults from tcp_keepalive_time = 7200, tcp_keepalive_intvl = 75, tcp_keepalive_probes = 9 */ + int keepidle = 60; // 1 minute + if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)) == -1) + { + perror("setsockopt TCP_KEEPIDLE"); + return -1; + } + + int keepintvl = 10; // 10 seconds + if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)) == -1) + { + perror("setsockopt TCP_KEEPINTVL"); + return -1; + } + + int keepcnt = 6; // 3 probes + if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)) == -1) + { + perror("setsockopt TCP_KEEPCNT"); + return -1; + } + return 0; +} From e2a9f0535bf3eaccd3031b8bcff94c470743eb6c Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Mon, 8 May 2023 15:21:38 +0200 Subject: [PATCH 03/12] Add docs --- include/networking.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/networking.h b/include/networking.h index b43ef26..56949dd 100644 --- a/include/networking.h +++ b/include/networking.h @@ -77,6 +77,16 @@ int tcp4_start_server(const char* port); */ int tcp6_start_server(const char* port); +/*! + * \brief Set socket options to enable TCP keepalive functions + * + * Change default values to ensure that lost connections are recognised and + * terminated. + * + * \param socket_file_descriptor Socket to which the changes are applied. + * + * \returns On success 0. On error -1. + */ int enable_tcp_keepalive(int socket_file_descriptor); #ifdef __cplusplus From f7bd1909e22494359c9b94e6bd71e4aae1af6b0a Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Sat, 13 May 2023 22:39:05 +0200 Subject: [PATCH 04/12] Make keepalive configurable by libjapi user --- include/japi.h | 15 +++++++++++++++ include/networking.h | 6 +++++- src/japi.c | 31 ++++++++++++++++++++++++++++--- src/networking.c | 24 ++++++++++++++---------- 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/include/japi.h b/include/japi.h index f2bd023..9d8e90e 100644 --- a/include/japi.h +++ b/include/japi.h @@ -52,6 +52,14 @@ typedef struct __japi_context { void *userptr; /*!< Pointer to user data */ uint16_t num_clients; /*!< Number of connected clients */ uint16_t max_clients; /*!< Number of maximal allowed clients */ + int tcp_keepalive_enable; /*!< Switch to enable keepalive mechanism in TCP server. + Configure using following attributes */ + int tcp_keepalive_time; /*!< The number of seconds a connection needs to be idle before + TCP begins sending out keep-alive probes. */ + int tcp_keepalive_intvl; /*!< The number of seconds between TCP keep-alive probes. */ + int tcp_keepalive_probes; /*!< The maximum number of TCP keep-alive probes to send before + giving up and killing the connection if no response is ob‐ + tained from the other end. */ pthread_mutex_t lock; /*!< Mutual access lock */ struct __japi_request *requests; /*!< Pointer to the JAPI request list */ struct __japi_pushsrv_context *push_services; /*!< Pointer to the JAPI push service list */ @@ -164,6 +172,13 @@ int japi_set_max_allowed_clients(japi_context *ctx, uint16_t num); */ int japi_include_args_in_response(japi_context *ctx, bool include_args); +/* TODO change defaults from tcp_keepalive_time = 7200, tcp_keepalive_intvl = 75, tcp_keepalive_probes = 9 */ +int japi_set_tcp_keepalive(japi_context *ctx, + int tcp_keepalive_enable, + int tcp_keepalive_time, + int tcp_keepalive_intvl, + int tcp_keepalive_probes); + /*! * \brief Shutdown the JAPI server * diff --git a/include/networking.h b/include/networking.h index 56949dd..1628be1 100644 --- a/include/networking.h +++ b/include/networking.h @@ -87,7 +87,11 @@ int tcp6_start_server(const char* port); * * \returns On success 0. On error -1. */ -int enable_tcp_keepalive(int socket_file_descriptor); +int enable_tcp_keepalive(int socket_file_descriptor, + int tcp_keepalive_enable, + int tcp_keepalive_time, + int tcp_keepalive_intvl, + int tcp_keepalive_probes); #ifdef __cplusplus } diff --git a/src/japi.c b/src/japi.c index 21c6cca..0a1ecb7 100644 --- a/src/japi.c +++ b/src/japi.c @@ -288,6 +288,10 @@ japi_context* japi_init(void *userptr) ctx->num_clients = 0; ctx->max_clients = 0; ctx->include_args_in_response = false; + ctx->tcp_keepalive_enable = 0; + ctx->tcp_keepalive_time = 7200; + ctx->tcp_keepalive_intvl = 75; + ctx->tcp_keepalive_probes = 9; ctx->shutdown = false; /* Initialize mutex */ @@ -338,6 +342,20 @@ int japi_include_args_in_response(japi_context *ctx, bool include_args) return 0; } +/* change defaults from tcp_keepalive_time = 7200, tcp_keepalive_intvl = 75, tcp_keepalive_probes = 9 */ +int japi_set_tcp_keepalive(japi_context *ctx, + int tcp_keepalive_enable, + int tcp_keepalive_time, + int tcp_keepalive_intvl, + int tcp_keepalive_probes) +{ + // TODO Error handling + ctx->tcp_keepalive_enable = tcp_keepalive_enable; + ctx->tcp_keepalive_time = tcp_keepalive_time; + ctx->tcp_keepalive_intvl = tcp_keepalive_intvl; + ctx->tcp_keepalive_probes = tcp_keepalive_probes; +} + /* * Add new client element to list */ @@ -462,9 +480,16 @@ int japi_start_server(japi_context *ctx, const char *port) return -1; } - /* TODO make optional */ - if (enable_tcp_keepalive(server_socket) < 0){ - return -1; + if (ctx->tcp_keepalive_enable) + { + if (enable_tcp_keepalive(server_socket, + ctx->tcp_keepalive_enable, + ctx->tcp_keepalive_time, + ctx->tcp_keepalive_intvl, + ctx->tcp_keepalive_probes) < 0) + { + return -1; + } } int ret; diff --git a/src/networking.c b/src/networking.c index b31f5a2..dacf977 100644 --- a/src/networking.c +++ b/src/networking.c @@ -117,33 +117,37 @@ int tcp6_start_server(const char* port) return tcp_start_server_on_addr_family(port, AF_INET6); } -int enable_tcp_keepalive(int socket_file_descriptor) +int enable_tcp_keepalive(int socket_file_descriptor, + int tcp_keepalive_enable, + int tcp_keepalive_time, + int tcp_keepalive_intvl, + int tcp_keepalive_probes) { - int yes = 1; /* Set SO_KEEPALIVE to 1(True) */ - if (setsockopt(socket_file_descriptor, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) + if (setsockopt(socket_file_descriptor, SOL_SOCKET, SO_KEEPALIVE, &tcp_keepalive_enable, sizeof(tcp_keepalive_enable)) == -1) { perror("ERROR: setsocktop SO_KEEPALIVE"); fprintf(stderr, "WARNING: Keeping connection alive wont be possible. \n"); /* The programm can go on. */ }; - /* change defaults from tcp_keepalive_time = 7200, tcp_keepalive_intvl = 75, tcp_keepalive_probes = 9 */ - int keepidle = 60; // 1 minute - if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)) == -1) + if (!tcp_keepalive_enable) + { + return 0; + } + + if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_time, sizeof(tcp_keepalive_time)) == -1) { perror("setsockopt TCP_KEEPIDLE"); return -1; } - int keepintvl = 10; // 10 seconds - if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)) == -1) + if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(tcp_keepalive_intvl)) == -1) { perror("setsockopt TCP_KEEPINTVL"); return -1; } - int keepcnt = 6; // 3 probes - if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)) == -1) + if (setsockopt(socket_file_descriptor, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_probes, sizeof(tcp_keepalive_probes)) == -1) { perror("setsockopt TCP_KEEPCNT"); return -1; From 1171bc2823bb8dd617271f55c7cf5b69790819d3 Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Sat, 13 May 2023 23:32:57 +0200 Subject: [PATCH 05/12] Extend docs --- include/japi.h | 19 ++++++++++++++++++- include/networking.h | 8 ++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/include/japi.h b/include/japi.h index 9d8e90e..20043e4 100644 --- a/include/japi.h +++ b/include/japi.h @@ -172,7 +172,24 @@ int japi_set_max_allowed_clients(japi_context *ctx, uint16_t num); */ int japi_include_args_in_response(japi_context *ctx, bool include_args); -/* TODO change defaults from tcp_keepalive_time = 7200, tcp_keepalive_intvl = 75, tcp_keepalive_probes = 9 */ +/*! + * \brief Enable or disable TCP keepalive and set the relevant parameters. + * + * Default values depend on the system. E.g. for Debian 11 `tcp_keepalive_time + * = 7200`, `tcp_keepalive_intvl = 75`, `tcp_keepalive_probes = 9` + * + * \param ctx JAPI context + * \param tcp_keepalive_enable Switch to enable keepalive mechanism in TCP + * server. Configure using following attributes + * \param tcp_keepalive_time The number of seconds a connection needs to be + * idle before TCP begins sending out keep-alive probes. + * \param tcp_keepalive_intvl The number of seconds between TCP keep-alive probes. + * \param tcp_keepalive_probes The maximum number of TCP keep-alive probes to send + * before giving up and killing the connection if no + * response is obtained from the other end. + * + * \returns On success, zero is returned. On invalid parameters, -1 is returned. + */ int japi_set_tcp_keepalive(japi_context *ctx, int tcp_keepalive_enable, int tcp_keepalive_time, diff --git a/include/networking.h b/include/networking.h index 1628be1..e6b81a0 100644 --- a/include/networking.h +++ b/include/networking.h @@ -84,6 +84,14 @@ int tcp6_start_server(const char* port); * terminated. * * \param socket_file_descriptor Socket to which the changes are applied. + * \param tcp_keepalive_enable Switch to enable keepalive mechanism in TCP + * server. Configure using following attributes + * \param tcp_keepalive_time The number of seconds a connection needs to be + * idle before TCP begins sending out keep-alive probes. + * \param tcp_keepalive_intvl The number of seconds between TCP keep-alive probes. + * \param tcp_keepalive_probes The maximum number of TCP keep-alive probes to send + * before giving up and killing the connection if no + * response is obtained from the other end. * * \returns On success 0. On error -1. */ From 943311dff0023e628c3f2890080ecb65037e8589 Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Fri, 26 May 2023 17:08:16 +0200 Subject: [PATCH 06/12] Add test and error handling for parameters --- README.md | 7 +++++++ src/japi.c | 20 +++++++++++++++++++- test/japi_test.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 94cf978..a6e98db 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,13 @@ A Makefile is generated. Run 'make' to build the libjapi libraries. A shared and a static library is built. +### Tests +The testsuite from [googletest](https://github.com/google/googletest) is used. To run tests, call + + $ cd build + $ make run_test + + ## Demo You can clone the [demo project](https://git01.iis.fhg.de/ks-ip-lib/software/libjapi-demo), with examples for all features from the repository listed below: diff --git a/src/japi.c b/src/japi.c index 0a1ecb7..1a0e5d2 100644 --- a/src/japi.c +++ b/src/japi.c @@ -349,11 +349,29 @@ int japi_set_tcp_keepalive(japi_context *ctx, int tcp_keepalive_intvl, int tcp_keepalive_probes) { - // TODO Error handling + /* Error handling */ + if (ctx == NULL) { + fprintf(stderr, "ERROR: JAPI context is NULL.\n"); + return -1; + } + if (tcp_keepalive_time < 0) { + fprintf(stderr, "ERROR: tcp_keepalive_time has to be positive or 0.\n"); + return -1; + } + if (tcp_keepalive_intvl <= 0) { + fprintf(stderr, "ERROR: tcp_keepalive_intvl has to be positive.\n"); + return -1; + } + if (tcp_keepalive_probes < 0) { + fprintf(stderr, "ERROR: tcp_keepalive_probes has to be positive or 0.\n"); + return -1; + } + ctx->tcp_keepalive_enable = tcp_keepalive_enable; ctx->tcp_keepalive_time = tcp_keepalive_time; ctx->tcp_keepalive_intvl = tcp_keepalive_intvl; ctx->tcp_keepalive_probes = tcp_keepalive_probes; + return 0; } /* diff --git a/test/japi_test.cc b/test/japi_test.cc index 1434ca6..15d254b 100644 --- a/test/japi_test.cc +++ b/test/japi_test.cc @@ -22,6 +22,9 @@ Copyright (c) 2023 Fraunhofer IIS #include #include +#include +#include +#include extern "C"{ #include @@ -29,6 +32,7 @@ extern "C"{ #include #include #include +#include #include } @@ -449,3 +453,45 @@ TEST(JAPI_Push_Service,PushServiceDestroy) /* Pass bad push service context */ EXPECT_EQ(japi_pushsrv_destroy(NULL),-1); } + +TEST(JAPI, TcpKeepAliveSetup) +{ + japi_context *ctx; + + ctx = japi_init(NULL); + + /* Activate keepalive in socket. Test different parameters */ + EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 10, 6), 0); + EXPECT_EQ(japi_set_tcp_keepalive(ctx, 0, 60, 10, 6), 0); + EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 10.7, 6), -1); // int only + EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, -60, 10, 0), -1); // positive values only + + /* Test if options are set correctly in context*/ + EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 10, 6), 0); + EXPECT_EQ(ctx->tcp_keepalive_enable, 1); + EXPECT_EQ(ctx->tcp_keepalive_time, 60); + EXPECT_EQ(ctx->tcp_keepalive_intvl, 10); + EXPECT_EQ(ctx->tcp_keepalive_probes, 6); + + /* Test if sockoptions are actually set */ + int opt_val; + socklen_t opt_len = sizeof(opt_val); + int server_socket = tcp_start_server("1234"); + ASSERT_EQ(enable_tcp_keepalive(server_socket, + ctx->tcp_keepalive_enable, + ctx->tcp_keepalive_time, + ctx->tcp_keepalive_intvl, + ctx->tcp_keepalive_probes), 0); + + ASSERT_EQ(getsockopt(server_socket, SOL_SOCKET, SO_KEEPALIVE, &opt_val, &opt_len), 0); + EXPECT_EQ(opt_val, 1); + + ASSERT_EQ(getsockopt(server_socket, IPPROTO_TCP, TCP_KEEPIDLE, &opt_val, &opt_len), 0); + EXPECT_EQ(opt_val, 60); + + ASSERT_EQ(getsockopt(server_socket, IPPROTO_TCP, TCP_KEEPINTVL, &opt_val, &opt_len), 0); + EXPECT_EQ(opt_val, 10); + + ASSERT_EQ(getsockopt(server_socket, IPPROTO_TCP, TCP_KEEPCNT, &opt_val, &opt_len), 0); + EXPECT_EQ(opt_val, 6); +} From af593acd18a937eff30d743f4bb0456e8d108bf6 Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Fri, 26 May 2023 17:32:14 +0200 Subject: [PATCH 07/12] fixup tests --- test/japi_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/japi_test.cc b/test/japi_test.cc index 15d254b..c7e11df 100644 --- a/test/japi_test.cc +++ b/test/japi_test.cc @@ -463,11 +463,11 @@ TEST(JAPI, TcpKeepAliveSetup) /* Activate keepalive in socket. Test different parameters */ EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 10, 6), 0); EXPECT_EQ(japi_set_tcp_keepalive(ctx, 0, 60, 10, 6), 0); - EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 10.7, 6), -1); // int only + EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 0.999, 6), -1); // tcp_keepalive_intvl > 0, implicit type conversion EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, -60, 10, 0), -1); // positive values only /* Test if options are set correctly in context*/ - EXPECT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 10, 6), 0); + ASSERT_EQ(japi_set_tcp_keepalive(ctx, 1, 60, 10, 6), 0); EXPECT_EQ(ctx->tcp_keepalive_enable, 1); EXPECT_EQ(ctx->tcp_keepalive_time, 60); EXPECT_EQ(ctx->tcp_keepalive_intvl, 10); From f70bf11d46ee529ac86f501ab223a2015c03c025 Mon Sep 17 00:00:00 2001 From: Katja Vornberger Date: Fri, 19 Jan 2024 12:01:16 +0100 Subject: [PATCH 08/12] WIP: testing keepalive functionality draft --- test/japi_test.cc | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/japi_test.cc b/test/japi_test.cc index 078fcf7..44c3411 100644 --- a/test/japi_test.cc +++ b/test/japi_test.cc @@ -601,3 +601,77 @@ TEST(JAPI, TcpKeepAliveSetup) 0); EXPECT_EQ(opt_val, 6); } + +TEST(JAPI, JAPI_TcpKeepAliveFunctionality) +{ + // this test recreates big parts from `japi_start_server` + // cannot use `japi_start_server` here as it is blocking, so cannot cut connection + // while running. + // Do I need handling for SIGPIPE once keepalive strikes? + + // create and configure server socket + int server_socket = tcp_start_server("1234"); + ASSERT_GT(server_socket, 0); + + ASSERT_EQ(enable_tcp_keepalive(server_socket, 1, 1, 1, 2), 0); + + // Start listening for incoming connections + ASSERT_EQ(listen(server_socket, 10), 0) << "Failed to listen on server socket"; + + // Create a client socket + int clientSock = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(clientSock, -1) << "Failed to create client socket"; + + // Connect the client socket to the server + struct sockaddr_in serverAddr = {0}; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(1234); + serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + // ASSERT_EQ(connect(clientSock, (struct sockaddr *)&serverAddr, + // sizeof(serverAddr)), 0) << "Failed to connect client socket"; + + // Close the client socket abruptly to simulate an ungraceful disconnection + // close(clientSock); + + // Accept the incoming connection on the server side + // int acceptedSock = accept(server_socket, NULL, NULL); + // ASSERT_NE(acceptedSock, -1) << "Failed to accept connection on server socket"; + + // Set a timeout for receiving data on the accepted socket + // struct timeval timeout; + // timeout.tv_sec = 5; + // timeout.tv_usec = 0; + // setsockopt(acceptedSock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, + // sizeof(timeout)); + + // Check if the client disconnected ungracefully by receiving data from the + // client + char buf[1024]; + // ssize_t numBytes = recv(acceptedSock, buf, sizeof(buf), 0); + // ASSERT_EQ(numBytes, 0) << "Client did not disconnect ungracefully"; + + // wait until keepalive kicks in, then check if socket is closed automatically + // sleep(3); + + // reconnect from client side + ASSERT_EQ(connect(clientSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)), + 0) + << "Failed to connect client socket (2)"; + // Accept again the incoming connection on the server side + int acceptedSock = accept(server_socket, NULL, NULL); + ASSERT_NE(acceptedSock, -1) << "Failed to accept connection on server socket (2)"; + // Send the message to the server + const char *message = "Hello, server!"; + ssize_t num_bytes_sent = send(clientSock, message, strlen(message), 0); + ASSERT_EQ(num_bytes_sent, strlen(message)) << "Message was not fully sent"; + + // Check if the client disconnected ungracefully by receiving data from the client + ssize_t numBytes_recv = recv(acceptedSock, buf, sizeof(buf), 0); + ASSERT_EQ(numBytes_recv, strlen(message)) << "Sent message was not fully received"; + + // Close the accepted socket + close(acceptedSock); + + // Close the server socket + close(server_socket); +} From 5f4d6bb2d0fb9287e6d718ede6f3f8a2674ef441 Mon Sep 17 00:00:00 2001 From: baronml Date: Wed, 6 Nov 2024 08:54:59 +0100 Subject: [PATCH 09/12] Add debug print before accepting new clients to detect rejection of new clients when maximum number of clients is reached --- src/japi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/japi.c b/src/japi.c index 639a48d..a216662 100644 --- a/src/japi.c +++ b/src/japi.c @@ -658,6 +658,9 @@ int japi_start_server(japi_context *ctx, const char *port) perror("ERROR: accept() failed\n"); return -1; } + + prntdbg("Client socket to add: %d, current number of clients %d\n", client_socket, ctx->num_clients); + if (ctx->max_clients == 0 || ctx->num_clients < ctx->max_clients) { japi_add_client(ctx, client_socket); prntdbg("client %d added\n", client_socket); From 5f1733c747228cb2b79508a432d0b04a8f72f1a8 Mon Sep 17 00:00:00 2001 From: baronml Date: Wed, 6 Nov 2024 10:03:16 +0100 Subject: [PATCH 10/12] Close server socket after test --- test/japi_test.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/japi_test.cc b/test/japi_test.cc index 44c3411..e688cc5 100644 --- a/test/japi_test.cc +++ b/test/japi_test.cc @@ -600,6 +600,8 @@ TEST(JAPI, TcpKeepAliveSetup) ASSERT_EQ(getsockopt(server_socket, IPPROTO_TCP, TCP_KEEPCNT, &opt_val, &opt_len), 0); EXPECT_EQ(opt_val, 6); + + close(server_socket); } TEST(JAPI, JAPI_TcpKeepAliveFunctionality) From 866223d867fed4b470fdcd550ec9be762c9930ac Mon Sep 17 00:00:00 2001 From: baronml Date: Wed, 6 Nov 2024 12:34:14 +0100 Subject: [PATCH 11/12] Reworked JAPI_TcpKeepAliveFunctionality test to start an acutal JAPI-Server in a thread, setup first client and seocnd client with a maximum allowed number of clients of 1. The acutal test of the TCP-Keep alive functionality is still not performed, since the client 1 socket is closed gracefully (May be mocking is needed). --- test/japi_test.cc | 132 +++++++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/test/japi_test.cc b/test/japi_test.cc index e688cc5..554242f 100644 --- a/test/japi_test.cc +++ b/test/japi_test.cc @@ -604,76 +604,102 @@ TEST(JAPI, TcpKeepAliveSetup) close(server_socket); } +static void *serverThread(void *arg) +{ + japi_context *ctx = (japi_context*) arg; + + japi_start_server(ctx, "1234"); + + pthread_exit(0); +} + TEST(JAPI, JAPI_TcpKeepAliveFunctionality) { - // this test recreates big parts from `japi_start_server` - // cannot use `japi_start_server` here as it is blocking, so cannot cut connection - // while running. - // Do I need handling for SIGPIPE once keepalive strikes? + japi_context *ctx; - // create and configure server socket - int server_socket = tcp_start_server("1234"); - ASSERT_GT(server_socket, 0); + ctx = japi_init(NULL); + + /* Remove any ungracefull disconnected client socket after + * 1s + 2 * 1s = 4s timeout. */ + ASSERT_EQ(japi_set_tcp_keepalive(ctx, 1, 1, 1, 2), 0); - ASSERT_EQ(enable_tcp_keepalive(server_socket, 1, 1, 1, 2), 0); + /* Allow only one client at a time. */ + ASSERT_EQ(japi_set_max_allowed_clients(ctx, 1), 0); - // Start listening for incoming connections - ASSERT_EQ(listen(server_socket, 10), 0) << "Failed to listen on server socket"; + pthread_t tid; + pthread_create(&tid, NULL, serverThread, (void*) ctx); - // Create a client socket - int clientSock = socket(AF_INET, SOCK_STREAM, 0); - ASSERT_NE(clientSock, -1) << "Failed to create client socket"; + /* Wait for the server to start. */ + /* TODO: Use mutex */ + sleep(2); // Connect the client socket to the server struct sockaddr_in serverAddr = {0}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(1234); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); - // ASSERT_EQ(connect(clientSock, (struct sockaddr *)&serverAddr, - // sizeof(serverAddr)), 0) << "Failed to connect client socket"; - // Close the client socket abruptly to simulate an ungraceful disconnection - // close(clientSock); - - // Accept the incoming connection on the server side - // int acceptedSock = accept(server_socket, NULL, NULL); - // ASSERT_NE(acceptedSock, -1) << "Failed to accept connection on server socket"; + int clientSock1, clientSock2; + const char *message = "{'japi_request': 'japi_cmd_list'}\n"; + ssize_t num_bytes_sent, numBytes_recv; + char buf[1024]; - // Set a timeout for receiving data on the accepted socket - // struct timeval timeout; - // timeout.tv_sec = 5; - // timeout.tv_usec = 0; - // setsockopt(acceptedSock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, - // sizeof(timeout)); + /***** CLIENT 1 *****/ + // Create a client socket and send request + clientSock1 = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(clientSock1, -1) << "Failed to create client socket1"; + ASSERT_EQ(connect(clientSock1, (struct sockaddr *)&serverAddr, + sizeof(serverAddr)), 0) << "Failed to connect client socket1"; + num_bytes_sent = send(clientSock1, message, strlen(message), 0); + ASSERT_EQ(num_bytes_sent, strlen(message)) << "Message was not fully sent"; - // Check if the client disconnected ungracefully by receiving data from the - // client - char buf[1024]; - // ssize_t numBytes = recv(acceptedSock, buf, sizeof(buf), 0); - // ASSERT_EQ(numBytes, 0) << "Client did not disconnect ungracefully"; - - // wait until keepalive kicks in, then check if socket is closed automatically - // sleep(3); - - // reconnect from client side - ASSERT_EQ(connect(clientSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)), - 0) - << "Failed to connect client socket (2)"; - // Accept again the incoming connection on the server side - int acceptedSock = accept(server_socket, NULL, NULL); - ASSERT_NE(acceptedSock, -1) << "Failed to accept connection on server socket (2)"; - // Send the message to the server - const char *message = "Hello, server!"; - ssize_t num_bytes_sent = send(clientSock, message, strlen(message), 0); + // Receive reponse of server + memset(buf, 0, sizeof(buf)); + numBytes_recv = recv(clientSock1, buf, sizeof(buf), 0); + ASSERT_NE(numBytes_recv, -1) << "Receive message failed"; + ASSERT_NE(numBytes_recv, 0) << "Received message is empty"; + + /* Close the first client socket ungracefully. + * TODO: Find a way to do this and replace close(). */ + close(clientSock1); + + /********************/ + + /* Wait for the keep-Alive mechanism to kill client 1 socket. */ + sleep(5); + + /***** CLIENT 2 *****/ + + /* Connecting a second client to the server with succesfull send and receive requires the frist client to + * to be remove properly. If the TCP-KEEP alive mechanism was not working properly, the first client will + * be still alive after the waiting time and following receive of client 2 will signal an empty message + * */ + // Create a client socket and send request + clientSock2 = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(clientSock2, -1) << "Failed to create client socket2"; + ASSERT_EQ(connect(clientSock2, (struct sockaddr *)&serverAddr, + sizeof(serverAddr)), 0) << "Failed to connect client socket2"; + num_bytes_sent = send(clientSock2, message, strlen(message), 0); ASSERT_EQ(num_bytes_sent, strlen(message)) << "Message was not fully sent"; - // Check if the client disconnected ungracefully by receiving data from the client - ssize_t numBytes_recv = recv(acceptedSock, buf, sizeof(buf), 0); - ASSERT_EQ(numBytes_recv, strlen(message)) << "Sent message was not fully received"; + // Receive reponse of server + memset(buf, 0, sizeof(buf)); + numBytes_recv = recv(clientSock2, buf, sizeof(buf), 0); + ASSERT_NE(numBytes_recv, -1) << "Receive message failed"; + ASSERT_NE(numBytes_recv, 0) << "Received message is empty"; - // Close the accepted socket - close(acceptedSock); + close(clientSock2); - // Close the server socket - close(server_socket); + /********************/ + + /* Wait for debug messages just in case server is faster with closing. */ + sleep(1); + + /* Signal shutdown to server. */ + ctx->shutdown = true; + + /* Wait for server to end. */ + pthread_join(tid, NULL); + japi_destroy(ctx); + return; } From 12cd2880d88808b101c058bd20a5a68c13460be4 Mon Sep 17 00:00:00 2001 From: baronml Date: Wed, 6 Nov 2024 15:23:10 +0100 Subject: [PATCH 12/12] Add Description to JAPI_TcpKeepAliveFunctionality unit test --- test/japi_test.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/japi_test.cc b/test/japi_test.cc index 554242f..58e32b8 100644 --- a/test/japi_test.cc +++ b/test/japi_test.cc @@ -615,6 +615,13 @@ static void *serverThread(void *arg) TEST(JAPI, JAPI_TcpKeepAliveFunctionality) { + /* Testing TCP-Keep-Alive functionality: Set max number of clients to 1, set + * keep-alive settings and setup a JAPI server. First client is going to connect, + * send data disconnects and disconnects ungracefully. When the TCP keep-alive works + * the ungraceful disconnected client will be closed after the configured interval. + * Client 2 should then be able to connect and send data (Will not be possible when + * client1 socket still is alive since max number of clients is restricted to 1). + * */ japi_context *ctx; ctx = japi_init(NULL);