From 41f042fbc60f872698ae75c722ff42191d359dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Obrembski?= Date: Tue, 19 Jan 2021 16:13:43 +0100 Subject: [PATCH 1/2] Added printing statistics on SIGUSR1 signal --- src/io.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/io.c b/src/io.c index 577623b4..ae092e13 100644 --- a/src/io.c +++ b/src/io.c @@ -118,6 +118,14 @@ static void destroy_ssl_locks(void) // global variable to pass signal out of its handler volatile sig_atomic_t sig_received = 0; +enum tunnel_state *current_state; +static uint64_t total_received_bytes; +static uint64_t total_transmitted_bytes; +static struct in_addr ip_addr; +static struct in_addr ns1_addr; +static struct in_addr ns2_addr; +static char dns_suffix[MAX_DOMAIN_LENGTH]; + int get_sig_received(void) { return (int)sig_received; @@ -261,6 +269,8 @@ static void *pppd_read(void *arg) log_debug("%s ---> gateway (%lu bytes)\n", PPP_DAEMON, packet->len); + total_transmitted_bytes += packet->len; + current_state = &tunnel->state; #if HAVE_USR_SBIN_PPPD log_packet("pppd: ", packet->len, pkt_data(packet)); #else @@ -292,6 +302,7 @@ static void *pppd_write(void *arg) struct tunnel *tunnel = (struct tunnel *) arg; fd_set write_fd; + current_state = &tunnel->state; FD_ZERO(&write_fd); FD_SET(tunnel->pppd_pty, &write_fd); @@ -489,6 +500,7 @@ static void *ssl_read(void *arg) } log_debug("gateway ---> %s (%lu bytes)\n", PPP_DAEMON, packet->len); + total_received_bytes += packet->len; log_packet("gtw: ", packet->len, pkt_data(packet)); pool_push(&tunnel->ssl_to_pty_pool, packet); @@ -510,6 +522,14 @@ static void *ssl_read(void *arg) } strcat(line, "]"); log_info("Got addresses: %s\n", line); + memcpy(&ip_addr, &tunnel->ipv4.ip_addr, + sizeof(struct in_addr)); + memcpy(&ns1_addr, &tunnel->ipv4.ns1_addr, + sizeof(struct in_addr)); + memcpy(&ns2_addr, &tunnel->ipv4.ns2_addr, + sizeof(struct in_addr)); + memcpy(&dns_suffix, tunnel->ipv4.dns_suffix, + MAX_DOMAIN_LENGTH); } if (packet_is_end_negociation(packet)) { log_info("Negotiation complete.\n"); @@ -574,6 +594,7 @@ static void *if_config(void *arg) struct tunnel *tunnel = (struct tunnel *) arg; int timeout = 60000000; // one minute + current_state = &tunnel->state; log_debug("%s thread\n", __func__); // Wait for the right moment to configure IP interface @@ -604,11 +625,45 @@ static void *if_config(void *arg) return NULL; } +static void print_statistics(void) +{ + if (current_state != NULL) { + switch (*current_state) { + case STATE_DOWN: { + log_info("State: disconnected\n"); + break; + } + case STATE_CONNECTING: { + log_info("State: connecting\n"); + break; + } + case STATE_UP: { + log_info("State: connected\n"); + break; + } + case STATE_DISCONNECTING: { + log_info("State: disconnecting\n"); + break; + } + } + } + log_info("Received bytes: %lu\n", total_received_bytes); + log_info("Transmitted bytes: %lu\n", total_transmitted_bytes); + log_info("IP Address: %s\n", inet_ntoa(ip_addr)); + log_info("NSS1 Address: %s\n", inet_ntoa(ns1_addr)); + log_info("NSS2 Address: %s\n", inet_ntoa(ns2_addr)); + log_info("Domain: %s\n", dns_suffix); +} + static void sig_handler(int signo) { - sig_received = signo; - if (signo == SIGINT || signo == SIGTERM) - SEM_POST(&sem_stop_io); + if (signo == SIGUSR1) { + print_statistics(); + } else { + sig_received = signo; + if (signo == SIGINT || signo == SIGTERM) + SEM_POST(&sem_stop_io); + } } int io_loop(struct tunnel *tunnel) @@ -659,12 +714,14 @@ int io_loop(struct tunnel *tunnel) sigemptyset(&sigset); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGTERM); + sigaddset(&sigset, SIGUSR1); pthread_sigmask(SIG_BLOCK, &sigset, &oldset); #endif // Set signal handler if (signal(SIGINT, sig_handler) == SIG_ERR || - signal(SIGTERM, sig_handler) == SIG_ERR) + signal(SIGTERM, sig_handler) == SIG_ERR || + signal(SIGUSR1, sig_handler) == SIG_ERR) goto err_signal; // Ignore SIGHUP From 268c8aa327a26587ae4732de4eb6a1ba0ea440fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Obrembski?= Date: Wed, 20 Jan 2021 11:16:59 +0100 Subject: [PATCH 2/2] Added status-file and status-interval options --- src/config.c | 12 ++++++++ src/config.h | 3 ++ src/io.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++----- src/main.c | 34 ++++++++++++++++++++++- 4 files changed, 119 insertions(+), 8 deletions(-) diff --git a/src/config.c b/src/config.c index d30e7a63..bd23a75d 100644 --- a/src/config.c +++ b/src/config.c @@ -86,6 +86,8 @@ const struct vpn_config invalid_cfg = { .user_agent = NULL, .hostcheck = NULL, .check_virtual_desktop = NULL, + .status_file = {'\0'}, + .status_interval = 0 }; /* @@ -458,6 +460,12 @@ int load_config(struct vpn_config *cfg, const char *filename) } else if (strcmp(key, "check-virtual-desktop") == 0) { free(cfg->check_virtual_desktop); cfg->check_virtual_desktop = strdup(val); + } else if (strcmp(key, "status-file") == 0) { + strcpy(cfg->status_file, val); + } else if (strcmp(key, "status-interval") == 0) { + unsigned long status_interval = strtoul(val, NULL, 0); + + cfg->status_interval = status_interval; } else { log_warn("Bad key in config file: \"%s\".\n", key); goto err_free; @@ -614,4 +622,8 @@ void merge_config(struct vpn_config *dst, struct vpn_config *src) dst->hostcheck = src->hostcheck; if (src->check_virtual_desktop != invalid_cfg.check_virtual_desktop) dst->check_virtual_desktop = src->check_virtual_desktop; + if (src->status_file[0]) + strcpy(dst->status_file, src->status_file); + if (src->status_interval != invalid_cfg.status_interval) + dst->status_interval = src->status_interval; } diff --git a/src/config.h b/src/config.h index f7783e1f..54d685b9 100644 --- a/src/config.h +++ b/src/config.h @@ -64,6 +64,7 @@ struct x509_digest { #define OTP_SIZE 64 #define REALM_SIZE 63 #define PEM_PASSPHRASE_SIZE 31 +#define STATUS_FILE_SIZE 253 /* * RFC 6265 does not limit the size of cookies: @@ -96,6 +97,8 @@ struct vpn_config { char *pinentry; char iface_name[IF_NAMESIZE]; char realm[REALM_SIZE + 1]; + char status_file[STATUS_FILE_SIZE + 1]; + unsigned int status_interval; int set_routes; int set_dns; diff --git a/src/io.c b/src/io.c index ae092e13..20a706d9 100644 --- a/src/io.c +++ b/src/io.c @@ -125,6 +125,7 @@ static struct in_addr ip_addr; static struct in_addr ns1_addr; static struct in_addr ns2_addr; static char dns_suffix[MAX_DOMAIN_LENGTH]; +static const char *status_file; int get_sig_received(void) { @@ -594,7 +595,6 @@ static void *if_config(void *arg) struct tunnel *tunnel = (struct tunnel *) arg; int timeout = 60000000; // one minute - current_state = &tunnel->state; log_debug("%s thread\n", __func__); // Wait for the right moment to configure IP interface @@ -625,28 +625,47 @@ static void *if_config(void *arg) return NULL; } -static void print_statistics(void) +static void save_stats(void) { + FILE *fp; + const char *state_str; + if (current_state != NULL) { switch (*current_state) { case STATE_DOWN: { - log_info("State: disconnected\n"); + state_str = "State: disconnected"; break; } case STATE_CONNECTING: { - log_info("State: connecting\n"); + state_str = "State: connecting"; break; } case STATE_UP: { - log_info("State: connected\n"); + state_str = "State: connected"; break; } case STATE_DISCONNECTING: { - log_info("State: disconnecting\n"); + state_str = "State: disconnecting"; break; } } } + if (status_file[0] != '\0') { + fp = fopen(status_file, "w"); + if (fp != NULL) { + fprintf(fp, "State: %s\n", state_str); + fprintf(fp, "Received bytes: %lu\n", total_received_bytes); + fprintf(fp, "Transmitted bytes: %lu\n", total_transmitted_bytes); + fprintf(fp, "IP Address: %s\n", inet_ntoa(ip_addr)); + fprintf(fp, "NSS1 Address: %s\n", inet_ntoa(ns1_addr)); + fprintf(fp, "NSS2 Address: %s\n", inet_ntoa(ns2_addr)); + fprintf(fp, "Domain: %s\n", dns_suffix); + fclose(fp); + } else { + log_error(" Cannot create status file!\n"); + } + } + log_info("State: %s\n", state_str); log_info("Received bytes: %lu\n", total_received_bytes); log_info("Transmitted bytes: %lu\n", total_transmitted_bytes); log_info("IP Address: %s\n", inet_ntoa(ip_addr)); @@ -655,10 +674,33 @@ static void print_statistics(void) log_info("Domain: %s\n", dns_suffix); } +/* + * Thread to print statistics + */ +static void *status_thread(void *arg) +{ + struct tunnel *tunnel = (struct tunnel *) arg; + + log_debug("%s thread\n", __func__); + + // Wait for the right moment to configure IP interface + SEM_WAIT(&sem_if_config); + if (tunnel->config->status_file[0] != '\0') { + while (1) { + current_state = &tunnel->state; + save_stats(); + log_debug("Sleeping statistics thread for %u seconds...", + tunnel->config->status_interval); + sleep(tunnel->config->status_interval); + } + } + return NULL; +} + static void sig_handler(int signo) { if (signo == SIGUSR1) { - print_statistics(); + save_stats(); } else { sig_received = signo; if (signo == SIGINT || signo == SIGTERM) @@ -677,6 +719,7 @@ int io_loop(struct tunnel *tunnel) pthread_t ssl_read_thread; pthread_t ssl_write_thread; pthread_t if_config_thread; + pthread_t stats_thread; SEM_INIT(&sem_pppd_ready, 0, 0); SEM_INIT(&sem_if_config, 0, 0); @@ -688,6 +731,7 @@ int io_loop(struct tunnel *tunnel) init_ssl_locks(); init_hdlc(); + status_file = tunnel->config->status_file; /* * I noticed that using TCP_NODELAY (i.e. disabling Nagle's algorithm) @@ -759,6 +803,14 @@ int io_loop(struct tunnel *tunnel) goto err_thread; } + if (status_file[0] != '\0' && tunnel->config->status_interval != 0) { + ret = pthread_create(&stats_thread, NULL, status_thread, tunnel); + if (ret != 0) { + log_debug("Error creating status_thread: %s\n", strerror(ret)); + goto err_thread; + } + } + #if !HAVE_MACH_MACH_H // Restore the signal for the main thread pthread_sigmask(SIG_SETMASK, &oldset, NULL); @@ -772,6 +824,11 @@ int io_loop(struct tunnel *tunnel) ret = pthread_cancel(if_config_thread); if (ret != 0) log_debug("Error canceling if_config_thread: %s\n", strerror(ret)); + if (status_file[0] != '\0') { + ret = pthread_cancel(stats_thread); + if (ret != 0) + log_debug("Error canceling stats_thread: %s\n", strerror(ret)); + } ret = pthread_cancel(ssl_write_thread); if (ret != 0) @@ -791,6 +848,13 @@ int io_loop(struct tunnel *tunnel) log_info("Cleanup, joining threads...\n"); // failure to clean is a possible zombie thread, consider it fatal + if (status_file[0] != '\0' && tunnel->config->status_interval != 0) { + ret = pthread_join(stats_thread, NULL); + if (ret != 0) { + log_debug("Error joining stats_thread: %s\n", strerror(ret)); + fatal = 1; + } + } ret = pthread_join(if_config_thread, NULL); if (ret != 0) { log_debug("Error joining if_config_thread: %s\n", strerror(ret)); diff --git a/src/main.c b/src/main.c index 1ba130a2..11b69f0f 100644 --- a/src/main.c +++ b/src/main.c @@ -143,7 +143,11 @@ PPPD_USAGE \ " certificate will be matched against this value.\n" \ " is the X509 certificate's sha256 sum.\n" \ " This option can be used multiple times to trust\n" \ -" several certificates.\n" +" several certificates.\n" \ +" --status Create a status file with statistics.\n" \ +" --status-interval Interval between updating status file. \n " \ +" If status file is not set, then print it on log.\n" \ +" Printing is disabled by default.\n" #define help_options_part2 \ " --insecure-ssl Do not disable insecure SSL protocols/ciphers.\n" \ @@ -208,6 +212,8 @@ int main(int argc, char **argv) .use_syslog = 0, .half_internet_routes = 0, .persistent = 0, + .status_file = {'\0'}, + .status_interval = 0, #if HAVE_RESOLVCONF .use_resolvconf = USE_RESOLVCONF, #endif @@ -270,6 +276,8 @@ int main(int argc, char **argv) {"cipher-list", required_argument, NULL, 0}, {"min-tls", required_argument, NULL, 0}, {"seclevel-1", no_argument, &cli_cfg.seclevel_1, 1}, + {"status-file", required_argument, NULL, 0}, + {"status-interval", required_argument, NULL, 0}, #if HAVE_USR_SBIN_PPPD {"pppd-use-peerdns", required_argument, NULL, 0}, {"pppd-no-peerdns", no_argument, &cli_cfg.pppd_use_peerdns, 0}, @@ -307,6 +315,7 @@ int main(int argc, char **argv) /* If this option set a flag, do nothing else now. */ if (long_options[option_index].flag != 0) break; + log_debug("%s\n", long_options[option_index].name); if (strcmp(long_options[option_index].name, "version") == 0) { printf(VERSION "\n"); @@ -509,6 +518,26 @@ int main(int argc, char **argv) cli_cfg.set_dns = set_dns; break; } + if (strcmp(long_options[option_index].name, + "status-file") == 0) { + log_debug(" COpying %s\n", optarg); + strncpy(cli_cfg.status_file, optarg, STATUS_FILE_SIZE); + cli_cfg.status_file[STATUS_FILE_SIZE] = '\0'; + log_debug(" COpying %s\n", cli_cfg.status_file); + break; + } + if (strcmp(long_options[option_index].name, + "status-interval") == 0) { + long status_interval = strtol(optarg, NULL, 0); + + if (status_interval < 0 || status_interval > UINT_MAX) { + log_warn("Bad status_interval option: \"%s\"\n", + optarg); + break; + } + cli_cfg.status_interval = status_interval; + break; + } goto user_error; case 'h': printf("%s%s%s%s%s%s%s", usage, summary, @@ -625,6 +654,9 @@ int main(int argc, char **argv) log_debug_all("Config password = \"%s\"\n", cfg.password); if (cfg.otp[0] != '\0') log_debug("One-time password = \"%s\"\n", cfg.otp); + if (cfg.status_file[0] != '\0') + log_debug("Config status-file = \"%s\"\n", cfg.status_file); + log_debug("Config status-interval = \"%u\"\n", cfg.status_interval); if (geteuid() != 0) { log_error("This process was not spawned with root privileges, which are required.\n");