diff options
| author | arf20 <aruizfernandez05@gmail.com> | 2025-10-28 13:45:38 +0100 |
|---|---|---|
| committer | arf20 <aruizfernandez05@gmail.com> | 2025-10-28 13:45:38 +0100 |
| commit | 1fa9b6e34e5c92aa7fe492417cda2d21ec067ff8 (patch) | |
| tree | 3d8f8c28cbc0d5d8632c72449a4a45bdab278425 | |
| parent | 52dc621b426fcb55ef3887e67de08a612b15480d (diff) | |
| download | arfnet2-status-1fa9b6e34e5c92aa7fe492417cda2d21ec067ff8.tar.gz arfnet2-status-1fa9b6e34e5c92aa7fe492417cda2d21ec067ff8.zip | |
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | check.c | 135 | ||||
| -rw-r--r-- | check.h | 10 | ||||
| -rw-r--r-- | config.c | 90 | ||||
| -rw-r--r-- | config.h | 37 | ||||
| -rw-r--r-- | index.htm.tmpl | 4 | ||||
| -rw-r--r-- | main.c | 35 | ||||
| -rw-r--r-- | monitor.c | 235 | ||||
| -rw-r--r-- | monitor.cfg | 28 | ||||
| -rw-r--r-- | monitor.h | 35 |
10 files changed, 390 insertions, 221 deletions
@@ -3,7 +3,7 @@ CFLAGS = -g -Wall -pedantic LDFLAGS = -lmicrohttpd -lcurl -lm BIN = monitor -SRC = main.c monitor.c +SRC = main.c monitor.c config.c check.c $(BIN): $(SRC) $(CC) -o $@ $(CFLAGS) $^ $(LDFLAGS) @@ -0,0 +1,135 @@ +#include "check.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <curl/curl.h> + +static int +check_reach(const char *target) +{ + static char ping_cmd[256]; + + snprintf(ping_cmd, 256, "ping -W1 -c1 %s > /dev/null", target); + /* i know */ + if (system(ping_cmd) == 0) { + printf("reachable\n"); + return STATUS_UP; + } else { + printf("unreachable\n"); + return STATUS_DOWN; + } +} + +static int +check_dns(const char *name) +{ + static char dig_cmd[512]; + static char cmd_out[256]; + + snprintf(dig_cmd, 512, "dig +nocookie +short %s NS", name); + FILE *pf = popen(dig_cmd, "r"); + fread(cmd_out, 256, 1, pf); + pclose(pf); + + if (*cmd_out == '\0') { + printf("no ns\n"); + return STATUS_DOWN; + } + + *strchr(cmd_out, '\n') = '\0'; + + snprintf(dig_cmd, 512, "dig +nocookie +short @%s %s A", cmd_out, name); + pf = popen(dig_cmd, "r"); + fread(cmd_out, 256, 1, pf); + pclose(pf); + + if (*cmd_out == '\0' || !isdigit(*cmd_out)) { + printf("no a: %s\n", cmd_out); + return STATUS_DOWN; + } + + *strchr(cmd_out, '\n') = '\0'; + + printf("%s\n", cmd_out); + + return STATUS_UP; +} + +static size_t +write_data(void *ptr, size_t size, size_t nmemb, void *stream) +{ + return size * nmemb; +} + +static int +check_http(const char *endpoint) +{ + CURL *curl = curl_easy_init(); + if (!curl) { + fprintf(stderr, "Error allocating cURL handle\n"); + return -1; + } + + //curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1); + + curl_easy_setopt(curl, CURLOPT_URL, endpoint); + CURLcode curl_code = curl_easy_perform(curl); + if (curl_code != CURLE_OK) { + printf("curl_easy_perform() failed: %s\n", + curl_easy_strerror(curl_code)); + return STATUS_DOWN; + } + + long http_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_easy_cleanup(curl); + + printf("%ld\n", http_code); + + return http_code == 200 ? STATUS_UP : STATUS_DOWN; +} + +void +check_perform(target_t *targets, size_t targets_n) +{ + static size_t check_num = 0; + static char timestr[256]; + + time_t time_now = time(NULL); + struct tm *tm_now = gmtime(&time_now); + strftime(timestr, 256, "%F %T", tm_now); + + static const int (*check_funcs[])(const char *) = { + check_reach, + check_dns, + check_http + }; + + for (size_t i = 0; i < targets_n; i++) { + printf("[%s] [monitor] check #%ld %s: ", + timestr, check_num, targets[i].name); + targets[i].status = check_funcs[targets[i].type](targets[i].target); + } + + check_num++; +} + +int +check_init() +{ + CURLcode res = curl_global_init(CURL_GLOBAL_ALL); + if (res) { + fprintf(stderr, "Error initializing cURL: %s\n", + curl_easy_strerror(res)); + return -1; + } + + return 0; +} + + @@ -0,0 +1,10 @@ +#ifndef _CHECK_H +#define _CHECK_H + +#include "monitor.h" + +void check_perform(target_t *targets, size_t targets_n); +int check_init(); + +#endif /* _CHECK_H */ + diff --git a/config.c b/config.c new file mode 100644 index 0000000..35fa90e --- /dev/null +++ b/config.c @@ -0,0 +1,90 @@ +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +unsigned short port = DEFAULT_PORT; +char *log_path = DEFAULT_LOG_PATH; +monitor_config_t monitor_config = { .interval = DEFAULT_INTERVAL }; +alert_config_t alert_config; + +int +config_load(const char *conf_path) +{ + FILE *cfgf = fopen(conf_path, "r"); + if (!cfgf) { + fprintf(stderr, "Error opening config: %s\n", strerror(errno)); + return -1; + } + + fseek(cfgf , 0, SEEK_END); + size_t cfgsize = ftell(cfgf); + rewind(cfgf); + + monitor_config.target_config = malloc(cfgsize); + alert_config.alert_config = malloc(cfgsize); + + char *target_pos = monitor_config.target_config; + char *alert_pos = alert_config.alert_config; + + printf("config:\n"); + + char line[256]; + while (fgets(line, sizeof(line), cfgf)) { + if (*line == '#' || *line == '\n') + continue; + + char *separator = strchr(line, '='); + if (!separator) { + fprintf(stderr, "[config] malformed line: %s\n", line); + continue; + } + + *separator = '\0'; + + char *value = separator + 1; + + if (strcmp(line, "port") == 0) { + port = atoi(value); + printf("\tport: %d\n", port); + if (port == 0) { + fprintf(stderr, "[config] invalid port: %s\n", line); + return -1; + } + } else if (strcmp(line, "interval") == 0) { + monitor_config.interval = atoi(value); + printf("\tinterval: %ld\n", monitor_config.interval); + if (monitor_config.interval == 0) { + fprintf(stderr, "[config] invalid interval: %s\n", line); + return -1; + } + } else if (strcmp(line, "log") == 0) { + value[strlen(value) - 1] = '\0'; + log_path = strdup(value); + printf("\tlog path: %s\n", log_path); + } else if (strcmp(line, "from") == 0) { + value[strlen(value) - 1] = '\0'; + alert_config.from = strdup(value); + printf("\tfrom: %s\n", log_path); + } else if (strcmp(line, "target") == 0) { + target_pos += snprintf(target_pos, + cfgsize - (target_pos - monitor_config.target_config), + "%s", value); + } else if (strcmp(line, "alert") == 0) { + target_pos += snprintf(alert_pos, + cfgsize - (alert_pos - alert_config.alert_config), + "%s", value); + } else { + fprintf(stderr, "[config] unknown key: %s\n", line); + continue; + } + + } + + fclose(cfgf); + + return 0; +} + diff --git a/config.h b/config.h new file mode 100644 index 0000000..dbcfb97 --- /dev/null +++ b/config.h @@ -0,0 +1,37 @@ +#ifndef _CONFIG_H +#define _CONFIG_H + +#include <time.h> + + +#define BUFF_SIZE 65535 +#define INIT_VEC_CAPACITY 256 +#define CONFIG_PATH "monitor.cfg" + +#define DEFAULT_PORT 8888 +#define DEFAULT_INTERVAL 60 +#define DEFAULT_LOG_PATH "events.log" + +/* config types */ +typedef struct { + time_t interval; + char *target_config; +} monitor_config_t; + +typedef struct { + char *from; + char *alert_config; +} alert_config_t; + + +/* config objects */ +extern unsigned short port; +extern char *log_path; +extern monitor_config_t monitor_config; +extern alert_config_t alert_config; + + +int config_load(const char *conf_path); + +#endif /* _CONFIG_H */ + diff --git a/index.htm.tmpl b/index.htm.tmpl index 1c5f13a..3278b39 100644 --- a/index.htm.tmpl +++ b/index.htm.tmpl @@ -44,9 +44,9 @@ td.w-last { <main> <h2 class="center">Status Monitor</h2> <p>This webapp monitors the status of the main ARFNET services from outside the ARFNET network</p> - Services + <span>Services</span><span style="float: right;">Current time (UTC): %s</span> <table width="100%"> - <tr><th>Type</th><th>Service</th><th>Status</th><th>Uptime</th><th>%%up month</th><th>%%up total</th><th>Timeline</th></tr> + <tr><th>Type</th><th>Service</th><th>Status</th><th>Uptime</th><th>%%up month</th><th>%%up total</th><th>Timeline (week)</th></tr> %s </table> <br><br> @@ -12,14 +12,9 @@ #include <microhttpd.h> - - #include "monitor.h" - -#define RES_BUFF 65535 - - -#define PORT 8888 +#include "config.h" +#include "check.h" #define CFG_FILE "monitor.cfg" #define TMPL_FILE "index.htm.tmpl" @@ -37,7 +32,7 @@ enum MHD_Result answer_to_connection( size_t *upload_data_size, void **ptr ) { - char buff[RES_BUFF]; + char buff[BUFF_SIZE]; const struct sockaddr_in **coninfo = (const struct sockaddr_in**)MHD_get_connection_info( @@ -55,9 +50,11 @@ enum MHD_Result answer_to_connection( int ret; if (strcmp(method, "GET") == 0 && strcmp(url, "/") == 0) { - snprintf(buff, RES_BUFF, + snprintf(buff, BUFF_SIZE, index_format_template, - monitor_generate_status_html(), monitor_generate_incidents_html()); + timestr, + monitor_generate_status_html(), + monitor_generate_incidents_html()); response = MHD_create_response_from_buffer(strlen(buff), (void*)buff, MHD_RESPMEM_PERSISTENT); @@ -76,6 +73,8 @@ enum MHD_Result answer_to_connection( } int main() { + printf("ARFNET Status Monitor (C) 2025 under GPLv3\n"); + /* read index template file */ FILE *tf = fopen(TMPL_FILE, "r"); if (!tf) { @@ -90,12 +89,21 @@ int main() { fread(index_format_template, 1, tfs, tf); fclose(tf); + if (config_load(CONFIG_PATH) < 0) + return 1; + + if (check_init() < 0) + return 1; + + if (monitor_init(CFG_FILE, LOG_FILE) < 0) + return 1; + /* start server */ struct MHD_Daemon *daemon; daemon = MHD_start_daemon( MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_EPOLL, - PORT, NULL, NULL, + port, NULL, NULL, &answer_to_connection, NULL, MHD_OPTION_END); if (!daemon) { @@ -103,12 +111,11 @@ int main() { return 1; } - monitor_init(CFG_FILE, LOG_FILE); while (1) { - monitor_check(); + check_perform(targets, targets_size); monitor_update_events(LOG_FILE); - sleep(5); + sleep(monitor_config.interval); } } @@ -8,41 +8,10 @@ #include <math.h> #include <sys/param.h> -#include <curl/curl.h> - #include "monitor.h" -#define BUFF_SIZE 65535 -#define INIT_VEC_CAPACITY 256 - -typedef enum { - TYPE_REACH, - TYPE_DNS, - TYPE_WEB -} type_t; - -const char *type_str[] = { "reach", "dns", "web" }; - -typedef enum { - STATUS_DOWN, - STATUS_UP -} status_t; - -typedef struct { - time_t time; - status_t status; -} event_t; - -typedef struct { - type_t type; - char *name; - char *target; - - status_t status, status_1; +#include "config.h" - event_t *events; - size_t events_size, events_capacity; -} target_t; /* baked */ typedef struct { @@ -55,8 +24,10 @@ typedef struct { } incident_t; -static target_t targets[INIT_VEC_CAPACITY]; -static size_t targets_n = 0; +const char *type_str[] = { "reach", "dns", "web" }; + +target_t *targets = NULL; +size_t targets_size = 0, targets_capacity = INIT_VEC_CAPACITY; /* ordered*/ static incident_t *incidents = NULL; @@ -64,6 +35,8 @@ static size_t incidents_size = 0, incidents_capacity = 0; static char timestr[256]; +static char *status_str[] = { "down", "up" }; + static void target_events_push_ordered(target_t *target, const event_t *event) @@ -161,7 +134,7 @@ incidents_render() incidents_size = 0; - for (size_t i = 0; i < targets_n; i++) { + for (size_t i = 0; i < targets_size; i++) { /* iterate through downs */ for (size_t j = 0; j < targets[i].events_size; j++) { if (targets[i].events[j].status != STATUS_DOWN) @@ -200,8 +173,10 @@ incidents_render() } int -monitor_init(const char *cfg_path, const char *log_path) +monitor_init() { + targets = malloc(INIT_VEC_CAPACITY * sizeof(target_t)); + /* read monitor log */ FILE *logf = fopen(log_path, "r"); if (!logf) { @@ -220,23 +195,21 @@ monitor_init(const char *cfg_path, const char *log_path) logbuff[logread] = '\0'; - /* read monitoring configuration */ - FILE *cfgf = fopen(cfg_path, "r"); - if (!cfgf) { - fprintf(stderr, "Error opening config: %s\n", strerror(errno)); - return -1; - } - printf("monitor targets:\n"); tzset(); /* initialize tz conversion */ char line[256]; - while (fgets(line, sizeof(line), cfgf)) { - if (*line == '#' || *line == '\n') - continue; + char *src_line = monitor_config.target_config; + while (src_line != (void*)1) { + char *next_line = strchr(src_line, '\n'); + size_t linelen = next_line ? next_line - src_line : strlen(src_line); + strncpy(line, src_line, linelen); + line[linelen] = '\0'; + src_line = next_line + 1; - line[strlen(line) - 1] = '\0'; /* strip \n */ + if (*line == '\n' || *line == '\0') + continue; char *type = strtok(line, ","); char *name = strtok(NULL, ","); @@ -248,46 +221,37 @@ monitor_init(const char *cfg_path, const char *log_path) } if (strcmp(type, "reach") == 0) - targets[targets_n].type = TYPE_REACH; + targets[targets_size].type = TYPE_REACH; else if (strcmp(type, "dns") == 0) - targets[targets_n].type = TYPE_DNS; + targets[targets_size].type = TYPE_DNS; else if (strcmp(type, "web") == 0) - targets[targets_n].type = TYPE_WEB; + targets[targets_size].type = TYPE_WEB; - targets[targets_n].name = strdup(name); - targets[targets_n].target = strdup(target); - targets[targets_n].status = STATUS_DOWN; + targets[targets_size].name = strdup(name); + targets[targets_size].target = strdup(target); + targets[targets_size].status = STATUS_DOWN; /* read monitor logs */ - targets[targets_n].events_capacity = INIT_VEC_CAPACITY; - targets[targets_n].events_size = 0; - targets[targets_n].events = malloc(INIT_VEC_CAPACITY * sizeof(event_t)); + targets[targets_size].events_capacity = INIT_VEC_CAPACITY; + targets[targets_size].events_size = 0; + targets[targets_size].events = malloc(INIT_VEC_CAPACITY * sizeof(event_t)); - size_t event_n = target_events_load(&targets[targets_n], logbuff); + size_t event_n = target_events_load(&targets[targets_size], logbuff); printf("\t%s: %s,%s %ld events\n", - targets[targets_n].name, - type_str[targets[targets_n].type], - targets[targets_n].target, + targets[targets_size].name, + type_str[targets[targets_size].type], + targets[targets_size].target, event_n ); - targets_n++; + targets_size++; } - fclose(cfgf); - incidents = malloc(sizeof(incident_t) * INIT_VEC_CAPACITY); incidents_capacity = INIT_VEC_CAPACITY; incidents_size = 0; - CURLcode res = curl_global_init(CURL_GLOBAL_ALL); - if (res) { - fprintf(stderr, "Error initializing cURL: %s\n", - curl_easy_strerror(res)); - return -1; - } - return 0; } @@ -371,11 +335,6 @@ generate_timeline(const target_t *target, time_t since, time_t span) char *pos = buff; - static char *status_str[] = { - "down", - "up" - }; - pos += snprintf(pos, BUFF_SIZE - (pos - buff), "<table class=\"graph\" style=\"width:100%%;\"><tr>"); @@ -428,7 +387,7 @@ monitor_generate_status_html() char *pos = buff; - for (size_t i = 0; i < targets_n; i++) { + for (size_t i = 0; i < targets_size; i++) { float perc_month = target_perc_uptime_since(&targets[i], time(NULL) - (30*24*3600)); float perc_total = target_perc_uptime_total(&targets[i]); @@ -480,126 +439,10 @@ monitor_generate_incidents_html() return buff; } -static int -check_reach(const char *target) -{ - static char ping_cmd[256]; - - snprintf(ping_cmd, 256, "ping -W1 -c1 %s > /dev/null", target); - /* i know */ - if (system(ping_cmd) == 0) { - printf("reachable\n"); - return STATUS_UP; - } else { - printf("unreachable\n"); - return STATUS_DOWN; - } -} - -static int -check_dns(const char *name) -{ - static char dig_cmd[512]; - static char cmd_out[256]; - - snprintf(dig_cmd, 512, "dig +nocookie +short %s NS", name); - FILE *pf = popen(dig_cmd, "r"); - fread(cmd_out, 256, 1, pf); - pclose(pf); - - if (*cmd_out == '\0') { - printf("no ns\n"); - return STATUS_DOWN; - } - - *strchr(cmd_out, '\n') = '\0'; - - snprintf(dig_cmd, 512, "dig +nocookie +short @%s %s A", cmd_out, name); - pf = popen(dig_cmd, "r"); - fread(cmd_out, 256, 1, pf); - pclose(pf); - - if (*cmd_out == '\0' || !isdigit(*cmd_out)) { - printf("no a: %s\n", cmd_out); - return STATUS_DOWN; - } - - *strchr(cmd_out, '\n') = '\0'; - - printf("%s\n", cmd_out); - - return STATUS_UP; -} - -static size_t -write_data(void *ptr, size_t size, size_t nmemb, void *stream) -{ - return size * nmemb; -} - -static int -check_http(const char *endpoint) -{ - CURL *curl = curl_easy_init(); - if (!curl) { - fprintf(stderr, "Error allocating cURL handle\n"); - return -1; - } - - //curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1); - - curl_easy_setopt(curl, CURLOPT_URL, endpoint); - CURLcode curl_code = curl_easy_perform(curl); - if (curl_code != CURLE_OK) { - printf("curl_easy_perform() failed: %s\n", - curl_easy_strerror(curl_code)); - return STATUS_DOWN; - } - - long http_code; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - - curl_easy_cleanup(curl); - - printf("%ld\n", http_code); - - return http_code == 200 ? STATUS_UP : STATUS_DOWN; -} - -void -monitor_check() -{ - static size_t check_num = 0; - time_t time_now = time(NULL); - struct tm *tm_now = gmtime(&time_now); - strftime(timestr, 256, "%F %T", tm_now); - - static const int (*check_funcs[])(const char *) = { - check_reach, - check_dns, - check_http - }; - - for (size_t i = 0; i < targets_n; i++) { - printf("[%s] [monitor] check #%ld %s: ", - timestr, check_num, targets[i].name); - targets[i].status = check_funcs[targets[i].type](targets[i].target); - } - - check_num++; -} - static void commit_event(const char *log_path, const target_t *target, const event_t *event) { - static char *status_str[] = { - "down", - "up" - }; - char buff[256]; FILE *logf = fopen(log_path, "a"); @@ -619,19 +462,15 @@ commit_event(const char *log_path, const target_t *target, fclose(logf); } + void monitor_update_events(const char *log_path) { - static char *status_str[] = { - "down", - "up" - }; - time_t time_now = time(NULL); struct tm *tm_now = gmtime(&time_now); strftime(timestr, 256, "%F %T", tm_now); - for (size_t i = 0; i < targets_n; i++) { + for (size_t i = 0; i < targets_size; i++) { if (targets[i].events_size > 0 && ( targets[i].status == targets[i].events[targets[i].events_size - 1].status)) diff --git a/monitor.cfg b/monitor.cfg index cc85d17..03ca7b1 100644 --- a/monitor.cfg +++ b/monitor.cfg @@ -1,7 +1,25 @@ # Monitor config -# type,name,target -reach,ipv4,2.59.235.35 -dns,dns,arf20.com -web,http,http://arf20.com -web,https,https://arf20.com +# target=type,name,target + +# listen port +port=8888 + +# monitor interval in seconds (sleep) +interval=5 + +# monitor events log path +log=events.log + +# targets to monitor +target=reach,ipv4,2.59.235.35 +target=dns,dns,arf20.com +target=web,http,http://arf20.com +target=web,https,https://arf20.com + +# email From +from=status@arf20.com + +# what to alert +alert=api,https://arf20.com/%s +alert=email,arf20@arf20.com @@ -1,7 +1,40 @@ #ifndef _MONITOR_H #define _MONITOR_H -int monitor_init(const char *cfg_file, const char *log_file); +#include <time.h> + +typedef enum { + TYPE_REACH, + TYPE_DNS, + TYPE_WEB +} type_t; + +typedef enum { + STATUS_DOWN, + STATUS_UP +} status_t; + +typedef struct { + time_t time; + status_t status; +} event_t; + +typedef struct { + type_t type; + char *name; + char *target; + + status_t status, status_1; + + event_t *events; + size_t events_size, events_capacity; +} target_t; + + +extern target_t *targets; +extern size_t targets_size; + +int monitor_init(); const char *monitor_generate_status_html(); const char *monitor_generate_incidents_html(); void monitor_check(); |
