summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt7
-rw-r--r--logging.c26
-rw-r--r--logging.h25
-rw-r--r--netmon.c238
4 files changed, 296 insertions, 0 deletions
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 <stdio.h>
+
+
+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 <time.h>
+
+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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+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 <check_interval>] [-n <max_failure>] [-l <log_file>] [-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;
+}