Initial Commit
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Editor directories
|
||||
.ecode/
|
||||
|
||||
# Project files
|
||||
build/
|
||||
config.ini
|
||||
messages
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "deps/vector"]
|
||||
path = deps/vector
|
||||
url = https://github.com/goldsborough/vector.git
|
||||
17
CMakeLists.txt
Normal file
17
CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(stethoscope
|
||||
VERSION 1.0
|
||||
LANGUAGES C)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
include_directories(${CMAKE_BINARY_DIR} ${PROJECT_SOURCE_DIR})
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
#file(COPY resources DESTINATION ${CMAKE_BINARY_DIR})
|
||||
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
|
||||
35
config.ini.example
Normal file
35
config.ini.example
Normal file
@@ -0,0 +1,35 @@
|
||||
## STETHOSCOPE EXAMPLE CONFIG
|
||||
|
||||
# The name of this Stethoscope instance. This name is used for the page title. (default: "My Stethoscope")
|
||||
service_name=My Stethoscope
|
||||
|
||||
# Port to listen on. This should be reverse proxied by something like httpd or nginx. (default: 7800)
|
||||
listen=7800
|
||||
|
||||
# Path of the webroot server uses to load asset paths like the stylesheet. (default: )
|
||||
webroot=
|
||||
|
||||
# Time in seconds to wait before attempting to reconnect to the endpoint. (default: 5)
|
||||
backoff=5
|
||||
|
||||
# These are example services.
|
||||
;[service.openssh]
|
||||
;name=OpenSSH Daemon
|
||||
;address=tcp://localhost:22
|
||||
|
||||
;[service.nginx]
|
||||
;name=Nginx
|
||||
;address=tcp://localhost:80,tcp://localhost:443,process://nginx,pidfile:///run/nginx.pid
|
||||
;backoff=1
|
||||
|
||||
;[service.email]
|
||||
;name=Email
|
||||
;address=tcp://localhost:25,tcp://localhost:143
|
||||
|
||||
;[service.tor]
|
||||
;name=Tor
|
||||
;address=tcp://127.0.0.1:9050
|
||||
|
||||
;[service.mumble]
|
||||
;name=mumble
|
||||
;address=tcp://localhost:64738,udp://localhost:64738
|
||||
1
deps/vector
vendored
Submodule
1
deps/vector
vendored
Submodule
Submodule deps/vector added at 40efe82256
5
src/CMakeLists.txt
Normal file
5
src/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
add_executable(${PROJECT_NAME}
|
||||
main.c
|
||||
../deps/vector/vector.c
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC microhttpd pthread inih mxml)
|
||||
183
src/main.c
Normal file
183
src/main.c
Normal file
@@ -0,0 +1,183 @@
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <pthread.h>
|
||||
#include <ini.h>
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#define CONFIG_SERVICE_PREFIX "service."
|
||||
|
||||
static void signal_handler(int signo) {
|
||||
if (signo == SIGINT || signo == SIGKILL) {
|
||||
running = 0;
|
||||
} else if (signo == SIGUSR1) {
|
||||
printf("USR1 signal detected! Not doing anything for now.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Only used to construct basic variables of a service before initialization
|
||||
struct PreService {
|
||||
char* id;
|
||||
char* name;
|
||||
char* text;
|
||||
char* endpoints;
|
||||
uint32_t backoff;
|
||||
};
|
||||
|
||||
static int config_handler(void* user, const char* section, const char* name, const char* value) {
|
||||
if (strcmp(section, "") == 0) {
|
||||
if (strcmp(name, "service_name") == 0) {
|
||||
daemon_name = malloc(strlen(value)+1);
|
||||
strcpy(daemon_name, value);
|
||||
} else if (strcmp(name, "webroot") == 0) {
|
||||
webroot = malloc(strlen(value)+1);
|
||||
strcpy(webroot, value);
|
||||
} else if (strcmp(name, "listen") == 0) {
|
||||
listen_port = atoi(value);
|
||||
} else if (strcmp(name, "backoff") == 0) {
|
||||
global_backoff = atoi(value);
|
||||
} else {
|
||||
printf("Unknown configuration entry \'%s\'\n", name);
|
||||
}
|
||||
} else if (strncmp(CONFIG_SERVICE_PREFIX, section, strlen(CONFIG_SERVICE_PREFIX)) == 0) {
|
||||
const char* id = section+strlen(CONFIG_SERVICE_PREFIX);
|
||||
Vector* tmp_services = (Vector*)user;
|
||||
struct PreService* current_service = NULL;
|
||||
|
||||
Iterator it = vector_begin(tmp_services);
|
||||
Iterator end = vector_end(tmp_services);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct PreService* current = *(struct PreService**)iterator_get(&it);
|
||||
if (strcmp(id, current->id) == 0) {
|
||||
current_service = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (current_service == NULL) {
|
||||
current_service = malloc(4096);
|
||||
current_service->id = malloc(strlen(id)+1);
|
||||
strcpy(current_service->id, id);
|
||||
current_service->backoff = 0;
|
||||
vector_push_back(tmp_services, ¤t_service);
|
||||
}
|
||||
if (strcmp(name, "name") == 0) {
|
||||
current_service->name = malloc(strlen(value)+1);
|
||||
strcpy(current_service->name, value);
|
||||
} else if (strcmp(name, "text") == 0) {
|
||||
current_service->text = malloc(strlen(value)+1);
|
||||
strcpy(current_service->text, value);
|
||||
} else if (strcmp(name, "address") == 0) {
|
||||
current_service->endpoints = malloc(strlen(value)+1);
|
||||
strcpy(current_service->endpoints, value);
|
||||
} else if (strcmp(name, "backoff") == 0) {
|
||||
current_service->backoff = atoi(value);
|
||||
} else {
|
||||
printf("Unknown configuration entry \'%s\' in section \'%s\'\n", name, id);
|
||||
}
|
||||
} else {
|
||||
printf("Unknown configuration section \'%s\'\n", section);
|
||||
}
|
||||
fflush(stdout);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void init_signals() {
|
||||
struct sigaction sa = {};
|
||||
sa.sa_handler = signal_handler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGKILL, &sa, NULL);
|
||||
sigaction(SIGUSR1, &sa, NULL);
|
||||
|
||||
sa.sa_handler = SIG_IGN;
|
||||
sigaction(SIGPIPE, &sa, NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
printf("Starting Stethoscope...\n");
|
||||
fflush(stdout);
|
||||
init_signals();
|
||||
|
||||
vector_setup(&status_timeline, 100, sizeof(struct StatusPeroid*));
|
||||
|
||||
char* config_path;
|
||||
for (int c = 1; c < argc; c++) {
|
||||
if (strcmp(argv[c], "-c") == 0) {
|
||||
if (argc > c) {
|
||||
config_path = argv[c+1];
|
||||
c++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
goto broken_args;
|
||||
}
|
||||
|
||||
if (argc <= 1) {
|
||||
broken_args:
|
||||
fprintf(stderr, "error: invalid number of arguments\nexample: %s -c <file>\n", argv[0]);
|
||||
fflush(stderr);
|
||||
return 3;
|
||||
}
|
||||
|
||||
// Load configuration file
|
||||
Vector tmp_services;
|
||||
vector_setup(&tmp_services, 255, sizeof(struct PreService*));
|
||||
printf("Loading configuration file: %s\n", config_path);
|
||||
int err = ini_parse(config_path, config_handler, &tmp_services);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "Failed to load configuration file: %i\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// initialize Services from temp vector
|
||||
vector_setup(&services, 255, sizeof(struct Service*));
|
||||
Iterator it = vector_begin(&tmp_services);
|
||||
Iterator end = vector_end(&tmp_services);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct PreService* preservice = *(struct PreService**)iterator_get(&it);
|
||||
printf("Initializing service \"%s\"\n", preservice->name);
|
||||
fflush(stdout);
|
||||
struct Service* service = malloc(8192);
|
||||
service->id = preservice->id;
|
||||
service->name = preservice->name;
|
||||
service->text = preservice->text;
|
||||
service->last_state = DOWN;
|
||||
service->backoff = preservice->backoff == 0 ? global_backoff : preservice->backoff;
|
||||
|
||||
char* endpoints = malloc(strlen(preservice->endpoints)+1);
|
||||
strcpy(endpoints, preservice->endpoints);
|
||||
char* token = strtok(endpoints, ",");
|
||||
vector_setup(&service->endpoints, 255, sizeof(struct Endpoint*));
|
||||
while (token != NULL) {
|
||||
add_endpoint(service, token);
|
||||
fflush(stdout);
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
vector_push_back(&services, &service);
|
||||
load_service(&service);
|
||||
}
|
||||
vector_resize(&services, services.size);
|
||||
vector_clear(&tmp_services);
|
||||
vector_destroy(&tmp_services);
|
||||
|
||||
struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_DUAL_STACK | MHD_USE_INTERNAL_POLLING_THREAD, listen_port, NULL, NULL, &http_response, NULL, MHD_OPTION_END);
|
||||
if (daemon == NULL) {
|
||||
fprintf(stderr, "Failed to start HTTP daemon\n");
|
||||
return 2;
|
||||
}
|
||||
running = 1;
|
||||
while (daemon != NULL && running == 1) {
|
||||
if (report_queued > 0) {
|
||||
report_queued = 0;
|
||||
report();
|
||||
} else sleep(global_backoff / 2);
|
||||
}
|
||||
printf("\nShutting down services...\n");
|
||||
fflush(stdout);
|
||||
MHD_stop_daemon(daemon);
|
||||
return 0;
|
||||
}
|
||||
213
src/main.h
Normal file
213
src/main.h
Normal file
@@ -0,0 +1,213 @@
|
||||
#ifndef H_MAIN
|
||||
#define H_MAIN
|
||||
#include <linux/limits.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <microhttpd.h>
|
||||
|
||||
#include "../deps/vector/vector.h"
|
||||
#include "mxml.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "services.c"
|
||||
|
||||
#define PAGE_INDEX 0
|
||||
#define PAGE_ASSET 1
|
||||
#define PAGE_ERROR 2
|
||||
|
||||
uint8_t running = 0;
|
||||
char* daemon_name = "Stethoscope";
|
||||
uint16_t listen_port = 7800;
|
||||
char* webroot = "share/";
|
||||
uint32_t global_backoff = 5;
|
||||
|
||||
char* cached_index;
|
||||
|
||||
int get_asset(char** buffer, const char* path, size_t* size) {
|
||||
*size = 0;
|
||||
if (strlen(webroot) + strlen(path) + 2 > PATH_MAX)
|
||||
return 403;
|
||||
|
||||
char full_path[PATH_MAX];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s", webroot, path);
|
||||
|
||||
char resolved_path[PATH_MAX];
|
||||
if (realpath(full_path, resolved_path) == NULL)
|
||||
return 403;
|
||||
|
||||
char resolved_base[PATH_MAX];
|
||||
if (realpath(webroot, resolved_base) == NULL)
|
||||
return 403;
|
||||
|
||||
size_t resolved_base_len = strlen(resolved_base);
|
||||
if (strncmp(resolved_path, resolved_base, resolved_base_len) != 0)
|
||||
return 400;
|
||||
|
||||
if (resolved_path[resolved_base_len] != '\0' && resolved_path[resolved_base_len] != '/')
|
||||
return 400;
|
||||
|
||||
FILE* asset = fopen(resolved_path, "rb");
|
||||
if (!asset) return 404;
|
||||
|
||||
fseek(asset, 0, SEEK_END);
|
||||
*size = ftell(asset);
|
||||
rewind(asset);
|
||||
|
||||
*buffer = (char*)malloc(*size + 1);
|
||||
if (!*buffer) {
|
||||
fclose(asset);
|
||||
*size = 0;
|
||||
return 500;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(*buffer, 1, *size, asset);
|
||||
fclose(asset);
|
||||
|
||||
if (bytes_read != *size) {
|
||||
free(*buffer);
|
||||
*size = 0;
|
||||
return 500;
|
||||
}
|
||||
|
||||
buffer[*size] = 0;
|
||||
|
||||
return 200;
|
||||
};
|
||||
|
||||
struct XMLServiceRow {
|
||||
struct Service* service;
|
||||
mxml_node_t* row;
|
||||
};
|
||||
|
||||
static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, const char* uri, const char* method, const char* version, const char* upload_Data, size_t* upload_data_size, void** req_cls) {
|
||||
const union MHD_ConnectionInfo *info = MHD_get_connection_info(conn, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
|
||||
const char *client_ip = {};
|
||||
uint16_t client_port = 0;
|
||||
if (info != NULL) {
|
||||
const struct sockaddr *addr = info->client_addr;
|
||||
char ip_str[INET6_ADDRSTRLEN];
|
||||
|
||||
if (addr->sa_family == AF_INET) {
|
||||
const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
|
||||
client_ip = inet_ntop(AF_INET, &addr4->sin_addr, ip_str, sizeof(ip_str));
|
||||
client_port = ntohs(addr4->sin_port);
|
||||
} else if (addr->sa_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
|
||||
client_ip = inet_ntop(AF_INET6, &addr6->sin6_addr, ip_str, sizeof(ip_str));
|
||||
client_port = ntohs(addr6->sin6_port);
|
||||
}
|
||||
} else client_ip = "?.?.?.?";
|
||||
|
||||
uint16_t response_code = 0;
|
||||
char* page;
|
||||
size_t page_size = 0;
|
||||
|
||||
if (strcmp(method, "GET") == 0) {
|
||||
if (strcmp(uri, "/") != 0)
|
||||
response_code = get_asset(&page, uri, &page_size);
|
||||
} else response_code = 400;
|
||||
|
||||
if (response_code != 200) {
|
||||
mxml_node_t* doc = mxmlNewXML("1.0");
|
||||
mxml_node_t* html = mxmlNewElement(doc, "html");
|
||||
mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml");
|
||||
mxmlElementSetAttr(html, "xml:lang", "en");
|
||||
mxmlElementSetAttr(html, "lang", "en");
|
||||
|
||||
mxml_node_t* head = mxmlNewElement(html, "head");
|
||||
mxml_node_t *title = mxmlNewElement(head, "title");
|
||||
mxmlNewText(title, 0, daemon_name);
|
||||
|
||||
mxml_node_t* meta_charset = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type");
|
||||
mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8");
|
||||
|
||||
mxml_node_t* meta_viewport = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_viewport, "name", "viewport");
|
||||
mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0");
|
||||
|
||||
mxml_node_t* stylesheet = mxmlNewElement(head, "link");
|
||||
mxmlElementSetAttr(stylesheet, "rel", "stylesheet");
|
||||
mxmlElementSetAttr(stylesheet, "href", "style.css");
|
||||
|
||||
mxml_node_t* body = mxmlNewElement(html, "body");
|
||||
|
||||
mxml_node_t* center = mxmlNewElement(body, "center");
|
||||
|
||||
if (strcmp(uri, "/") == 0 && response_code == 0) {
|
||||
mxml_node_t* header = mxmlNewElement(center, "h1");
|
||||
mxmlNewText(header, 0, daemon_name);
|
||||
|
||||
int row_index = 0;
|
||||
struct XMLServiceRow table_rows[services.size];
|
||||
|
||||
Iterator it = vector_begin(&services);
|
||||
Iterator end = vector_end(&services);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct Service* service = *(struct Service**)iterator_get(&it);
|
||||
mxml_node_t* table = mxmlNewElement(center, "table");
|
||||
mxmlElementSetAttr(table, "width", "100%");
|
||||
mxml_node_t* header_row = mxmlNewElement(table, "tr");
|
||||
mxml_node_t* header_cell = mxmlNewElement(table, "th");
|
||||
mxmlElementSetAttr(header_cell, "align", "left");
|
||||
mxmlNewText(header_cell, 0, service->name);
|
||||
|
||||
mxml_node_t* status_row = mxmlNewElement(table, "tr");
|
||||
|
||||
table_rows[row_index] = (struct XMLServiceRow){ service, status_row };
|
||||
row_index++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Iterator pit = vector_begin(&status_timeline);
|
||||
Iterator pend = vector_end(&status_timeline);
|
||||
for (; !iterator_equals(&pit, &pend); iterator_increment(&pit)) {
|
||||
struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&pit);
|
||||
|
||||
Iterator sit = vector_begin(&peroid->server_statuses);
|
||||
Iterator send = vector_end(&peroid->server_statuses);
|
||||
for (; !iterator_equals(&sit, &send); iterator_increment(&sit)) {
|
||||
struct ServerStatus* srvstat = *(struct ServerStatus**)iterator_get(&sit);
|
||||
|
||||
struct XMLServiceRow row_struct;
|
||||
for (int x = 0; x < services.size; x++)
|
||||
if (table_rows[x].service == srvstat->service) {
|
||||
row_struct = table_rows[x];
|
||||
break;
|
||||
}
|
||||
|
||||
mxml_node_t* cell = mxmlNewElement(row_struct.row, "td");
|
||||
mxmlElementSetAttr(cell, "_status", state_text(srvstat->state));
|
||||
char* ping_str = (char*)malloc(16);
|
||||
snprintf(ping_str, 16, "%f ms", srvstat->ping);
|
||||
mxmlElementSetAttr(cell, "_ping", ping_str);
|
||||
}
|
||||
}
|
||||
|
||||
response_code = 200;
|
||||
} else {
|
||||
mxml_node_t* error_header = mxmlNewElement(center, "h1");
|
||||
char* err_str = (char*)malloc(5);
|
||||
snprintf(err_str, 5, "%i", response_code);
|
||||
mxmlNewText(error_header, 0, err_str);
|
||||
}
|
||||
|
||||
page = mxmlSaveAllocString(doc, MXML_NO_CALLBACK);
|
||||
page_size = strlen(page);
|
||||
}
|
||||
|
||||
printf("[%lu] %s %s \"%s\" %s:%u %zu %zu %i\n", time(NULL), method, uri, version, client_ip, client_port, *upload_data_size, page_size, response_code);
|
||||
fflush(stdout);
|
||||
|
||||
struct MHD_Response* response = MHD_create_response_from_buffer_static(page_size, page);
|
||||
enum MHD_Result ret = MHD_queue_response(conn, response_code, response);
|
||||
MHD_destroy_response(response);
|
||||
return ret;
|
||||
};
|
||||
|
||||
#endif
|
||||
347
src/services.c
Normal file
347
src/services.c
Normal file
@@ -0,0 +1,347 @@
|
||||
#include <stdlib.h>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include "services.h"
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define POLLRDHUP 0x2000
|
||||
#endif
|
||||
|
||||
uint8_t report_queued = 0;
|
||||
uint64_t queued_time = 0;
|
||||
Vector services;
|
||||
Vector status_timeline; // of StatusPeroid*
|
||||
|
||||
void* run_endpoint(void* endpoint) {
|
||||
struct Endpoint* self = (struct Endpoint*)endpoint;
|
||||
struct timespec start, end;
|
||||
while (self->enabled) {
|
||||
int success = 0;
|
||||
if (strcmp(self->scheme, "tcp") == 0 || strcmp(self->scheme, "udp") == 0) {
|
||||
if (self->sockfd < 0) {
|
||||
struct addrinfo hints = {0}, *result, *rp;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
if (strcmp(self->scheme, "udp") == 0) {
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
} else {
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
}
|
||||
|
||||
char port_str[6];
|
||||
snprintf(port_str, sizeof(port_str), "%d", self->port);
|
||||
|
||||
int ret = getaddrinfo(self->target, port_str, &hints, &result);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(ret));
|
||||
queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
|
||||
sleep(self->backoff);
|
||||
continue;
|
||||
}
|
||||
|
||||
self->sockfd = -1;
|
||||
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
||||
int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (fd == -1) continue;
|
||||
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
if (strcmp(self->scheme, "tcp") == 0) {
|
||||
int opt = 1;
|
||||
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
|
||||
|
||||
int keepidle = 0;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
|
||||
|
||||
int keepintvl = self->backoff;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
|
||||
|
||||
int keepcnt = 1;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
|
||||
}
|
||||
|
||||
struct timespec connect_start;
|
||||
clock_gettime(CLOCK_REALTIME_ALARM, &connect_start);
|
||||
|
||||
ret = connect(fd, rp->ai_addr, rp->ai_addrlen);
|
||||
if (strcmp(self->scheme, "tcp") == 0) {
|
||||
if (errno == EINPROGRESS) {
|
||||
struct pollfd pfd = { .fd = fd, .events = POLLOUT };
|
||||
int pollret = poll(&pfd, 1, 5000);
|
||||
|
||||
if (pollret > 0 && (pfd.revents & POLLOUT)) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) != 0 && error != 0) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
char* dummy = 0;
|
||||
ssize_t sent = send(fd, &dummy, 1, MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
usleep(10000);
|
||||
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) != 0 || error != 0) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct pollfd pfd = { .fd = fd, .events = POLLIN | POLLERR };
|
||||
if (poll(&pfd, 1, (self->backoff > 0 ? self->backoff * 1000 : 10000)) > 0 && (pfd.revents & (POLLERR | POLLIN))) {
|
||||
if (recv(fd, NULL, sizeof(NULL), MSG_DONTWAIT) < 0) {
|
||||
self->last_errno = errno;
|
||||
if (self->last_errno == ECONNREFUSED || self->last_errno == EHOSTUNREACH || self->last_errno == ENETUNREACH) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME_ALARM, &now);
|
||||
|
||||
double ping = (now.tv_sec - connect_start.tv_sec) * 1000.0 + (now.tv_nsec - connect_start.tv_nsec) / 1000000.0;
|
||||
|
||||
self->sockfd = fd;
|
||||
self->last_errno = 0;
|
||||
queue_report((struct EndpointStatus){ self, UP, ping });
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
|
||||
if (self->sockfd < 0) {
|
||||
if (self->last_errno != errno) {
|
||||
self->last_errno = errno;
|
||||
fprintf(stderr, "%s://%s:%u connect failed: %i\n", self->scheme, self->target, self->port, errno);
|
||||
}
|
||||
queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
|
||||
sleep(self->backoff);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
struct pollfd pfd = { .fd = self->sockfd, .events = POLLERR | POLLHUP | POLLRDHUP };
|
||||
int pollret = poll(&pfd, 1, (self->backoff > 0 ? self->backoff * 1000 : 10000));
|
||||
|
||||
if (pollret < 0) {
|
||||
close(self->sockfd);
|
||||
self->sockfd = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pfd.revents & (POLLERR | POLLHUP | POLLRDHUP)) {
|
||||
close(self->sockfd);
|
||||
self->sockfd = -1;
|
||||
//queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(self->scheme, "udp") == 0) {
|
||||
if (send(self->sockfd, NULL, sizeof(NULL), 0) < 0) {
|
||||
close(self->sockfd);
|
||||
self->sockfd = -1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (pollret == 0) {
|
||||
time_t now = time(NULL);
|
||||
if (now - self->last_check >= self->backoff) {
|
||||
self->last_check = now;
|
||||
queue_report((struct EndpointStatus){ self, UP, self->last_ping });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
queue_report((struct EndpointStatus){ self, DOWN, -1.0 });
|
||||
}
|
||||
}
|
||||
|
||||
if (self->sockfd >= 0) {
|
||||
close(self->sockfd);
|
||||
self->sockfd = -1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void queue_report(struct EndpointStatus status) {
|
||||
status.endpoint->last_ping = status.ping;
|
||||
if (status.endpoint->last_state == status.state) return;
|
||||
status.endpoint->last_state = status.state;
|
||||
|
||||
uint64_t time = microtime();
|
||||
printf("[%lu] State of %s://%s:%u is %s\n", (time / 1000000), status.endpoint->scheme, status.endpoint->target, status.endpoint->port, state_text(status.state));
|
||||
fflush(stdout);
|
||||
|
||||
queued_time = time;
|
||||
report_queued = 1;
|
||||
}
|
||||
|
||||
void load_service(struct Service** self) {
|
||||
Iterator it = vector_begin(&(*self)->endpoints);
|
||||
Iterator end = vector_end(&(*self)->endpoints);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct Endpoint* endpoint = *(struct Endpoint**)iterator_get(&it);
|
||||
pthread_create(&endpoint->thread, NULL, &run_endpoint, (void *)endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t add_endpoint(struct Service* self, const char* uri) {
|
||||
if (!uri || !*uri) return 255;
|
||||
struct Endpoint* endpoint = (struct Endpoint*)malloc(2048);
|
||||
endpoint->service = self;
|
||||
endpoint->enabled = true;
|
||||
endpoint->sockfd = -1;
|
||||
endpoint->backoff = self->backoff;
|
||||
|
||||
const char* p = uri;
|
||||
const char* colon = strchr(p, ':');
|
||||
if (!colon) {
|
||||
fprintf(stderr, "Target URI is malformed: %s\n", p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t scheme_len = colon - p;
|
||||
endpoint->scheme = (char*)malloc(scheme_len+1);
|
||||
if (endpoint->scheme) {
|
||||
strncpy(endpoint->scheme, p, scheme_len);
|
||||
endpoint->scheme[scheme_len] = 0;
|
||||
for (int i = 0; i < scheme_len; i++)
|
||||
endpoint->scheme[i] = (char)tolower(endpoint->scheme[i]);
|
||||
}
|
||||
|
||||
p = colon + 1;
|
||||
if (!(*p == '/' && *(p+1) == '/')) {
|
||||
fprintf(stderr, "Target URI is malformed: %s\n", p);
|
||||
return 1;
|
||||
}
|
||||
p += 2;
|
||||
|
||||
const char* addr_start = p;
|
||||
const char* addr_end = NULL;
|
||||
if (*p == '[') {
|
||||
++p;
|
||||
addr_start = p;
|
||||
addr_end = strchr(p, ']');
|
||||
if (addr_end)
|
||||
p = addr_end + 1;
|
||||
else {
|
||||
fprintf(stderr, "Target URI is malformed: Unable to find end of IPv6 bracket in \"%s\"\n", addr_start);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!addr_end) {
|
||||
while (*p && *p != ':' && *p != '?' && *p != '#') ++p;
|
||||
addr_end = p;
|
||||
}
|
||||
|
||||
size_t addr_len = addr_end - addr_start;
|
||||
endpoint->target = (char*)malloc(addr_len+1);
|
||||
if (endpoint->target) {
|
||||
strncpy(endpoint->target, addr_start, addr_len);
|
||||
endpoint->target[addr_len] = 0;
|
||||
}
|
||||
|
||||
endpoint->port = 0;
|
||||
if (*p == ':') {
|
||||
++p;
|
||||
endpoint->port = (int)strtol(p, NULL, 10);
|
||||
}
|
||||
|
||||
printf("[%s] Added endpoint to service: %s, %s, %i\n", self->id, endpoint->scheme, endpoint->target, endpoint->port);
|
||||
vector_push_back(&self->endpoints, &endpoint);
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum State get_state(struct Service** self) {
|
||||
Iterator it = vector_begin(&(*self)->endpoints);
|
||||
Iterator end = vector_end(&(*self)->endpoints);
|
||||
int up_count = 0; int total_count = (*self)->endpoints.size;
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct Endpoint* endpoint = *(struct Endpoint**)iterator_get(&it);
|
||||
if (endpoint->last_state == UP)
|
||||
up_count++;
|
||||
}
|
||||
if (up_count == total_count) return UP;
|
||||
else if (up_count == 0) return DOWN;
|
||||
else return PARTIAL;
|
||||
}
|
||||
|
||||
void report() {
|
||||
if (status_timeline.size + 1 >= status_timeline.capacity)
|
||||
vector_pop_front(&status_timeline); // remove (what should be) oldest entry
|
||||
|
||||
struct StatusPeroid* peroid = malloc(512);
|
||||
peroid->time = queued_time;
|
||||
vector_setup(&peroid->server_statuses, services.size, sizeof(struct ServerStatus*));
|
||||
|
||||
Iterator it = vector_begin(&services);
|
||||
Iterator end = vector_end(&services);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct Service* service = *(struct Service**)iterator_get(&it);
|
||||
struct ServerStatus* server_status = update_server_status(&service);
|
||||
vector_push_back(&peroid->server_statuses, &server_status);
|
||||
}
|
||||
|
||||
vector_push_back(&status_timeline, &peroid);
|
||||
}
|
||||
|
||||
struct ServerStatus* update_server_status(struct Service** self) {
|
||||
struct Service* service = *self;
|
||||
double pings[service->endpoints.size] = {};
|
||||
|
||||
struct ServerStatus* server_status = malloc(512);
|
||||
server_status->service = service;
|
||||
enum State service_state = DOWN;
|
||||
|
||||
vector_setup(&server_status->endpoint_statuses, service->endpoints.size, sizeof(struct EndpointStatus));
|
||||
|
||||
int index = 0;
|
||||
Iterator it = vector_begin(&service->endpoints);
|
||||
Iterator end = vector_end(&service->endpoints);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct Endpoint* endpoint = *(struct Endpoint**)iterator_get(&it);
|
||||
struct EndpointStatus estatus;
|
||||
estatus.endpoint = endpoint;
|
||||
estatus.state = endpoint->last_state;
|
||||
pings[index] = estatus.ping = endpoint->last_ping;
|
||||
vector_push_back(&server_status->endpoint_statuses, &estatus);
|
||||
if (index == 0) service_state = endpoint->last_state;
|
||||
else if ((endpoint->last_state == DOWN && service_state == UP) || (endpoint->last_state == UP && service_state == DOWN))
|
||||
service_state = PARTIAL;
|
||||
index++;
|
||||
}
|
||||
|
||||
size_t pings_len = index;
|
||||
double ping_sum = 0;
|
||||
for (int i = 0; i < sizeof(pings)/sizeof(double); i++)
|
||||
if (pings[i] >= 0) {
|
||||
ping_sum+=pings[i];
|
||||
pings_len--;
|
||||
}
|
||||
double avg_ping = ping_sum / pings_len;
|
||||
|
||||
server_status->state = service_state;
|
||||
server_status->ping = avg_ping;
|
||||
return server_status;
|
||||
}
|
||||
|
||||
const char* state_text(enum State state) {
|
||||
if (state == UP)
|
||||
return "UP";
|
||||
else if (state == PARTIAL)
|
||||
return "PARTIAL";
|
||||
else return "DOWN";
|
||||
}
|
||||
80
src/services.h
Normal file
80
src/services.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef H_SERVICES
|
||||
#define H_SERVICES
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "../deps/vector/vector.h"
|
||||
#include "util.h"
|
||||
|
||||
enum State { DOWN, PARTIAL, UP };
|
||||
struct StatusPeroid;
|
||||
struct ServerStatus;
|
||||
struct EndpointStatus;
|
||||
struct Endpoint;
|
||||
struct Service;
|
||||
|
||||
void report();
|
||||
void* run_endpoint(void* endpoint);
|
||||
void queue_report(struct EndpointStatus status);
|
||||
void load_service(struct Service** self);
|
||||
uint8_t add_endpoint(struct Service* self, const char* uri);
|
||||
enum State get_state(struct Service** self);
|
||||
struct ServerStatus* update_server_status(struct Service** self);
|
||||
const char* state_text(enum State state);
|
||||
|
||||
struct StatusPeroid {
|
||||
unsigned long time;
|
||||
Vector server_statuses; // of ServerStatus*
|
||||
};
|
||||
|
||||
struct ServerStatus {
|
||||
struct Service* service;
|
||||
Vector endpoint_statuses; // of EndpointStatus*
|
||||
enum State state;
|
||||
double ping;
|
||||
};
|
||||
|
||||
struct EndpointStatus {
|
||||
struct Endpoint* endpoint;
|
||||
enum State state;
|
||||
double ping;
|
||||
};
|
||||
|
||||
struct Endpoint {
|
||||
struct Service* service;
|
||||
uint8_t enabled;
|
||||
uint8_t running;
|
||||
char* scheme;
|
||||
char* target;
|
||||
uint16_t port;
|
||||
uint32_t backoff;
|
||||
time_t last_check;
|
||||
enum State last_state;
|
||||
double last_ping;
|
||||
Vector status_history;
|
||||
pthread_t thread;
|
||||
int sockfd;
|
||||
int last_errno;
|
||||
};
|
||||
|
||||
struct Service {
|
||||
char* id;
|
||||
char* name;
|
||||
char* text;
|
||||
Vector endpoints;
|
||||
enum State last_state;
|
||||
double average_ping;
|
||||
uint32_t backoff;
|
||||
};
|
||||
|
||||
#endif
|
||||
14
src/util.h
Normal file
14
src/util.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef H_UTIL
|
||||
#define H_UTIL
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
static uint64_t microtime() {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
|
||||
};
|
||||
|
||||
#endif
|
||||
3
www/style.css
Normal file
3
www/style.css
Normal file
@@ -0,0 +1,3 @@
|
||||
td[_status=UP] { background-color: green; }
|
||||
td[_status=PARTIAL] { background-color: yellow; }
|
||||
td[_status=DOWN] { background-color: red; }
|
||||
Reference in New Issue
Block a user