From 94d7588c2045cbe76ec0287a5e92f1b4123d996f Mon Sep 17 00:00:00 2001 From: Keuin Date: Tue, 28 Dec 2021 05:37:54 +0800 Subject: Initial version. --- CMakeLists.txt | 7 ++ logging.c | 26 +++++++ logging.h | 25 ++++++ netmon.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 logging.c create mode 100644 logging.h create mode 100644 netmon.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..67e49f9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0) +project(netmon C) + +set(CMAKE_C_STANDARD 99) +add_compile_definitions(DEBUG) + +add_executable(netmon netmon.c logging.c logging.h) diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..2ad1704 --- /dev/null +++ b/logging.c @@ -0,0 +1,26 @@ +// +// Created by Keuin on 2021/12/28. +// + +#include "logging.h" +#include + + +void *log_init(const char *filename) { + FILE *fp = fopen(filename, "a"); + return fp; +} + +void log_free(void *logger) { + fflush(logger); + fclose(logger); +} + +void log_print(void *logger, const char *level, time_t ts, const char *filename, int lineno, const char *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)) + fflush(logger); + if (fprintf(stderr, "[%s][%s][%s][%d] %s\n", timestr, level, filename, lineno, msg)) + fflush(stderr); +} diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..71e1407 --- /dev/null +++ b/logging.h @@ -0,0 +1,25 @@ +// +// Created by Keuin on 2021/12/28. +// + +#ifndef NETMON_LOGGING_H +#define NETMON_LOGGING_H + +#include + +void *log_init(const char *filename); + +void log_free(void *logger); + +void log_print(void *logger, const char *level, time_t ts, const char *filename, int lineno, const char *msg); + +#ifdef DEBUG +#define log_debug(logger, msg) log_print((logger), "DEBUG", time(NULL), __FILE__, __LINE__, (msg)) +#else +#define log_debug(logger, msg) do { (logger); (msg); } while(0) +#endif +#define log_info(logger, msg) log_print((logger), "INFO", time(NULL), __FILE__, __LINE__, (msg)) +#define log_warning(logger, msg) log_print((logger), "WARN", time(NULL), __FILE__, __LINE__, (msg)) +#define log_error(logger, msg) log_print((logger), "ERROR", time(NULL), __FILE__, __LINE__, (msg)) + +#endif //NETMON_LOGGING_H diff --git a/netmon.c b/netmon.c new file mode 100644 index 0000000..f3104fd --- /dev/null +++ b/netmon.c @@ -0,0 +1,238 @@ +#include "logging.h" +#include +#include +#include +#include +#include + +#include +#include +#include + +const char *logfile = "netmon.log"; +int check_interval_seconds = 30; // seconds to sleep between checks +int max_check_failure = 5; // how many failures to reboot the system +int be_daemonize = 0; // should run as a daemon process + +volatile int require_reboot = 0; +void *logger = NULL; + +void daemonize() { + pid_t pid = 0; + pid_t sid = 0; + pid = fork(); + if (pid < 0) { + perror("fork()"); + log_error(logger, "fork() failed."); + exit(1); + } + if (pid > 0) { + char buf[32]; + sprintf(buf, "Child process: %d", pid); + log_init(buf); + exit(0); // exit parent process + } + // unmask the file mode + umask(0); + // set new session + sid = setsid(); + if (sid < 0) { + perror("setsid()"); + log_error(logger, "setsid() failed."); + exit(1); + } + chdir("/"); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); +} + +///** +// * Read and escape a string, then write to another buffer, with length limit. +// * @param si +// * @param so +// * @param maxlen +// */ +//void escape(const char *si, char *so, size_t maxlen) { +// size_t wb = 0; // bytes written +// char c[4] = {'\0'}; +// while ((c[0] = *si++) != '\0') { +// const char *s; +// switch (c[0]) { +// case '\r': +// s = "\r"; +// break; +// case '\n': +// s = "\n"; +// break; +// case '\t': +// s = "\t"; +// break; +// case '\v': +// s = "\v"; +// break; +// case '\b': +// s = "\b"; +// break; +// case '\a': +// s = "\a"; +// break; +// case '\f': +// s = "\f"; +// break; +// default: +// s = c; +// break; +// } +// strcpy(so, s) +// } +//} + +/** + * Check network availability. + * @return Zero if success, non-zero if failed. + */ +int check_network() { +#define RETURN(r) do { rv = (r); goto RET; } while(0) +#define READ_SIZE 32 + + int sock = -1, rv = 0; + struct sockaddr_in serv_addr; + const char *msg = "GET / HTTP/1.1\r\n" + "Host: www.gov.cn\r\n" + "\r\n"; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("socket()"); + log_error(logger, "socket() failed."); + RETURN(-1); + } + + const struct hostent *host = gethostbyname("www.gov.cn"); // TODO cache this + + if (!host) { + herror("gethostbyname()"); + log_error(logger, "Cannot resolve test host."); + RETURN(-1); + } + + if (host->h_length <= 0) { + log_error(logger, "Test host does not have at least one address."); + RETURN(-1); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(80); + serv_addr.sin_addr = **((struct in_addr **) host->h_addr_list); + + if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + log_error(logger, "connect() failed."); + RETURN(-1); + } + + ssize_t ss = 0; // send size + while (ss < strlen(msg)) { + ssize_t rd = send(sock, msg, strlen(msg), 0); + if (rd < 0) { + perror("send()"); + log_error(logger, "send() failed."); + RETURN(-1); + } + ss += rd; + } + + log_debug(logger, "HTTP request is sent. Reading response."); + char response[READ_SIZE] = {0}; + ssize_t rs = 0; // receive size + while (rs < READ_SIZE) { + ssize_t rd = read(sock, response, READ_SIZE - rs); + if (rd < 0) { + perror("read()"); + log_error(logger, "read() failed."); + RETURN(-1); + } else { + rs += rd; + } + } + + const char *expected = "HTTP/1.1 200 OK\r\n"; + if (memcmp(response, expected, strlen(expected)) != 0) { + char buf[64]; + snprintf(buf, 63, "Unexpected response: %s", response); + log_error(logger, buf); + RETURN(-1); + } + RETURN(0); + + RET: + if (sock >= 0) close(sock); + return rv; +#undef RETURN +#undef READ_SIZE +} + +void loop() { + // check network and sleep + // if fails too many times continuously, set require_reboot flag and return + int failures = 0; +#pragma clang diagnostic push +#pragma ide diagnostic ignored "EndlessLoop" + while (1) { + log_info(logger, "Check network."); + if (check_network() != 0) { + log_info(logger, "Network failure detected."); + ++failures; + } else { + log_info(logger, "Network is OK."); + failures = 0; + } + if (failures > max_check_failure) { + log_info(logger, "Max failure times exceeded."); + require_reboot = 1; + return; + } + log_info(logger, "Sleeping..."); + unsigned int t = check_interval_seconds; + while ((t = sleep(t))); + } +#pragma clang diagnostic pop +} + +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]); + ++i; // skip next value + } else if (!strcmp(argv[i], "-n")) { + max_check_failure = atoi(argv[i + 1]); + ++i; // skip next value + } else if (!strcmp(argv[i], "-l")) { + logfile = argv[i + 1]; + ++i; // skip next value + } else if (!strcmp(argv[i], "-d")) { + be_daemonize = 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]); + return invalid != 0; + } + } + + logger = log_init(logfile); + log_debug(logger, "DEBUG logging is enabled."); + if (be_daemonize) { + log_info(logger, "Daemonizing..."); + daemonize(); + } + log_info(logger, "netmon is started."); + loop(); + log_info(logger, "netmon is stopped."); + if (require_reboot) { + log_init("Trigger system reboot."); + system("reboot"); + } + log_free(logger); + return 0; +} -- cgit v1.2.3