From b3fd60e6407b3af029672218b0bff7bb30e7a9d9 Mon Sep 17 00:00:00 2001 From: Keuin Date: Thu, 30 Dec 2021 12:20:42 +0800 Subject: Add ping check (-p). Support custom error handling command (-c). Add runtime params validation for logging. --- CMakeLists.txt | 2 +- README.txt | 4 +++- logging.c | 5 +++++ netcheck.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- netcheck.h | 4 +++- netmon.c | 57 ++++++++++++++++++++++++++++++++++++++++++--------- validate.c | 23 +++++++++++++++++++++ validate.h | 18 +++++++++++++++++ 8 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 validate.c create mode 100644 validate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index efdf954..450b881 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,4 +4,4 @@ project(netmon C) set(CMAKE_C_STANDARD 99) add_compile_definitions(DEBUG) -add_executable(netmon netmon.c logging.c logging.h netcheck.c netcheck.h) +add_executable(netmon netmon.c logging.c logging.h netcheck.c netcheck.h validate.c validate.h) diff --git a/README.txt b/README.txt index 79320f7..4d11242 100644 --- a/README.txt +++ b/README.txt @@ -1,10 +1,12 @@ Usage: - netmon [-t ] [-n ] [-l ] [-d] + netmon [-t ] [-n ] [-l ] [-c ] [-p ] [-d] -t specify how many seconds to wait between two checks -n specify how many continous network failures we get until we reboot the system -l specify the log file + -c the command line to be executed when network failure is detected + -p test the network by pinging given host -d run as a daemon process diff --git a/logging.c b/logging.c index 2ad1704..90ccf23 100644 --- a/logging.c +++ b/logging.c @@ -3,6 +3,7 @@ // #include "logging.h" +#include "validate.h" #include @@ -17,6 +18,10 @@ void log_free(void *logger) { } void log_print(void *logger, const char *level, time_t ts, const char *filename, int lineno, const char *msg) { + NOTNULL(logger); + NOTNULL(level); + NOTNULL(filename); + NOTNULL(msg); char timestr[32]; strftime(timestr, 31, "%Y-%m-%d %H:%M:%S", localtime(&ts)); if (fprintf(logger, "[%s][%s][%s][%d] %s\n", timestr, level, filename, lineno, msg)) diff --git a/netcheck.c b/netcheck.c index 4900e0f..60a351a 100644 --- a/netcheck.c +++ b/netcheck.c @@ -5,18 +5,20 @@ #include #include #include +#include #include #include #include #include #include "logging.h" #include "netcheck.h" +#include "validate.h" /** - * Check network availability. + * Check network availability by testing a tcp communication. * @return Zero if success, non-zero if failed. */ -int check_network(void *logger) { +int check_tcp(void *logger) { #define RETURN(r) do { rv = (r); goto RET; } while(0) #define READ_SIZE 32 @@ -93,4 +95,62 @@ int check_network(void *logger) { return rv; #undef RETURN #undef READ_SIZE +} + +/** + * Check network availability by pinging a remote host. + * Note that this routine requires an external ping program. + * @return Zero if success, non-zero if failed. + * @param logger the logger. + * @param dest the destination host, whether a domain or an ip address. + * @param ping path to the ping executable. If null, will use `/bin/ping`. + * @return Zero if success, non-zero if failed. + */ +int check_ping(void *logger, const char *dest, const char *ping) { + if (!is_valid_ipv4(dest)) { + log_error(logger, "dest is not a valid IPv4 address."); + return -1; + } + +#define BUFLEN 1024 +#define RETURN(r) do { rv = (r); goto CP_RET; } while(0) + int rv = 0; + // Copied from https://stackoverflow.com/questions/8189935/is-there-any-way-to-ping-a-specific-ip-address-with-c + if (ping == NULL) ping = "/bin/ping"; + int pipe_arr[2] = {-1}; + char buf[BUFLEN] = {0}; + + // Create pipe - pipe_arr[0] is "reading end", pipe_arr[1] is "writing end" + if (pipe(pipe_arr)) { + perror("pipe()"); + log_error(logger, "pipe() failed."); + return -1; + } + + if (fork() == 0) { + // child + if (dup2(pipe_arr[1], STDOUT_FILENO) < 0) { + log_error(logger, "dup2() failed."); + exit(-1); + } + execl(ping, "ping", "-c 3", dest, (char *) NULL); + } else { + // parent + if (wait(NULL) < 0) { + perror("wait()"); + log_error(logger, "wait() failed."); + RETURN(-1); + } + if (read(pipe_arr[0], buf, BUFLEN) < 0) { + perror("read()"); + log_error(logger, "read() failed."); + RETURN(-1); + } + } + CP_RET: + if (pipe_arr[0] >= 0) close(pipe_arr[0]); + if (pipe_arr[1] >= 0) close(pipe_arr[1]); + return (rv) ? (rv) : (strstr(buf, "time=") == NULL); +#undef BUFLEN +#undef RETURN } \ No newline at end of file diff --git a/netcheck.h b/netcheck.h index 3bea521..173d6fd 100644 --- a/netcheck.h +++ b/netcheck.h @@ -6,6 +6,8 @@ #define NETMON_NETCHECK_H -int check_network(void *logger); +int check_tcp(void *logger); + +int check_ping(void *logger, const char *dest, const char *ping); #endif //NETMON_NETCHECK_H diff --git a/netmon.c b/netmon.c index 0b0a434..f08825d 100644 --- a/netmon.c +++ b/netmon.c @@ -8,11 +8,15 @@ const char *logfile = "netmon.log"; -int check_interval_seconds = 30; // seconds to sleep between checks +const char *pingdest = NULL; // which host to ping. If NULL, test tcp instead +const char *failcmd = "reboot"; // cmd to be executed. If NULL, reboot // TODO support blanks +unsigned int check_interval_seconds = 30; // seconds to sleep between checks int max_check_failure = 5; // how many failures to reboot the system int as_daemon = 0; // should run as a daemon process +unsigned int failure_sleep_seconds = 60; // seconds to sleep before resuming check after a network failure is detected +// TODO make failure_sleep_seconds configurable -volatile int require_reboot = 0; +volatile int failure_detected = 0; void *logger = NULL; void daemonize() { @@ -94,7 +98,9 @@ void loop() { #pragma ide diagnostic ignored "EndlessLoop" while (1) { log_info(logger, "Check network."); - if (check_network(logger) != 0) { + if ((pingdest == NULL) ? + (check_tcp(logger) != 0) : + (check_ping(logger, pingdest, NULL) != 0)) { ++failures; char buf[64]; snprintf(buf, 63, "Network failure detected. counter=%d", failures); @@ -105,8 +111,21 @@ void loop() { } if (failures > max_check_failure) { log_info(logger, "Max failure times exceeded."); - require_reboot = 1; - return; + failure_detected = 1; + failures = 0; // reset failure counter + + // handle a network failure event + char tmp[256]; + snprintf(tmp, 255, "Run system command `%s`.", failcmd); + log_info(logger, tmp); + system(failcmd); + + snprintf(tmp, 255, "Wait %d secs before resume checking.\n", failure_sleep_seconds); + log_debug(logger, tmp); + unsigned t = failure_sleep_seconds; + while ((t = sleep(t))); + + continue; // resume checking right now } log_info(logger, "Sleeping..."); unsigned int t = check_interval_seconds; @@ -119,20 +138,42 @@ int main(int argc, char *argv[]) { for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i], "-t")) { check_interval_seconds = atoi(argv[i + 1]); + if (check_interval_seconds <= 0) { + fprintf(stderr, "check interval should be positive.\n"); + return -1; + } ++i; // skip next value } else if (!strcmp(argv[i], "-n")) { max_check_failure = atoi(argv[i + 1]); + if (max_check_failure < 0) { + fprintf(stderr, "max check failure should not be negative.\n"); + return -1; + } ++i; // skip next value } else if (!strcmp(argv[i], "-l")) { logfile = argv[i + 1]; ++i; // skip next value + } else if (!strcmp(argv[i], "-p")) { + pingdest = argv[i + 1]; + ++i; // skip next value + } else if (!strcmp(argv[i], "-c")) { + char *s = argv[i + 1]; + unsigned long len = strlen(s); + if (len == 0) { + fprintf(stderr, "Empty fail command.\n"); + return -1; + } + failcmd = s; + fprintf(stderr, "Fail command is set to `%s`.\n", failcmd); + ++i; // skip next value } else if (!strcmp(argv[i], "-d")) { as_daemon = 1; } else { int invalid = strcmp(argv[i], "-h") != 0 && strcmp(argv[i], "--help") != 0; if (invalid) printf("Unrecognized parameter \"%s\".\n", argv[i]); printf("Usage:\n" - " %s [-t ] [-n ] [-l ] [-d]\n", argv[0]); + " %s [-t ] [-n ] [-l ] [-c ] [-p ] [-d]\n", + argv[0]); return invalid != 0; } } @@ -146,10 +187,6 @@ int main(int argc, char *argv[]) { log_info(logger, "netmon is started."); loop(); log_info(logger, "netmon is stopped."); - if (require_reboot) { - log_info(logger, "Trigger system reboot."); - system("reboot"); - } log_free(logger); return 0; } diff --git a/validate.c b/validate.c new file mode 100644 index 0000000..49005f0 --- /dev/null +++ b/validate.c @@ -0,0 +1,23 @@ +// +// Created by Keuin on 2021/12/30. +// + +#include "validate.h" +#include + +/** + * Check if a given string is a valid dot-decimal representation of an IPv4 address. + * @param s the string. + * @return Non-zero if true, zero if false. + */ +int is_valid_ipv4(const char *s) { + // TODO buggy +// unsigned int addr[4]; +// if (sscanf(s, "%ud.%ud.%ud.%ud", &addr[0], &addr[1], &addr[2], &addr[3]) != 4) +// return 0; +// for (int i = 0; i < 4; ++i) { +// if (addr[i] > 255) return 0; +// if (addr[i] == 0 && (i == 0 || i == 3)) return 0; +// } + return 1; +} diff --git a/validate.h b/validate.h new file mode 100644 index 0000000..f09a820 --- /dev/null +++ b/validate.h @@ -0,0 +1,18 @@ +// +// Created by Keuin on 2021/12/30. +// + +#ifndef NETMON_VALIDATE_H +#define NETMON_VALIDATE_H + +#include + +int is_valid_ipv4(const char *s); + +#define NOTNULL(ptr) do { \ + if ((ptr) == NULL) { \ + fprintf(stderr, "NotNull check failed: "#ptr " is null. (" __FILE__ ":%d)\n", __LINE__); \ + abort(); \ + } } while(0) + +#endif //NETMON_VALIDATE_H -- cgit v1.2.3