Compare commits
33
Commits
main
..
9355fcd309
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9355fcd309 | ||
|
|
bd940dd806 | ||
|
|
051a0ef4dd | ||
|
|
e5ae782433 | ||
|
|
dbd4fe22ed | ||
|
|
378f64fc75 | ||
|
|
14ebbbec3f | ||
|
|
23ea7644ce | ||
|
|
833c78debc | ||
|
|
01830e2eb2 | ||
|
|
320a0cfc4c | ||
|
|
62d5169d9f | ||
|
|
78da301299 | ||
|
|
08b9dbb214 | ||
|
|
c8c1f2b595 | ||
|
|
ae6c5a9ac2 | ||
|
|
b1409609d0 | ||
|
|
59acad776c | ||
|
|
0c8c04ecd4 | ||
|
|
51f2ade810 | ||
|
|
bbfc462ec2 | ||
|
|
559c22fffa | ||
|
|
eb2decd36d | ||
|
|
38cf154a78 | ||
|
|
a9a6abb531 | ||
|
|
43fcdc4e26 | ||
|
|
dd30b5d18d | ||
|
|
ce81738292 | ||
|
|
59d075d8e0 | ||
|
|
d60dea1c44 | ||
|
|
29d8272fc5 | ||
|
|
57e00feec1 | ||
|
|
ff6c1be582 |
Executable → Regular
+1
-19
@@ -1,4 +1,3 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
@@ -21,7 +20,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc make libssl-dev libsqlite3-dev bc
|
||||
sudo apt-get install -y gcc make libssl-dev libsqlite3-dev
|
||||
|
||||
- name: Build
|
||||
run: make all
|
||||
@@ -29,22 +28,5 @@ jobs:
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
- name: Build legacy
|
||||
run: make legacy
|
||||
|
||||
- name: Clean
|
||||
run: make clean
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc make libssl-dev libsqlite3-dev bc gcovr lcov
|
||||
|
||||
- name: Run coverage
|
||||
run: make coverage
|
||||
|
||||
Executable → Regular
-114
@@ -1,114 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Version 0.14.0 - 2026-01-27
|
||||
|
||||
add zero-copy forwarding using splice syscall on linux
|
||||
|
||||
**Changes:** 4 files, 105 lines
|
||||
**Languages:** C (105 lines)
|
||||
|
||||
## Version 0.13.0 - 2026-01-27
|
||||
|
||||
extract base64 decoding into separate module
|
||||
|
||||
**Changes:** 37 files, 7660 lines
|
||||
**Languages:** C (7469 lines), Other (191 lines)
|
||||
|
||||
## Version 0.12.0 - 2026-01-27
|
||||
|
||||
The server now rejects HTTP pipelined requests with a 400 Bad Request error.
|
||||
|
||||
**Changes:** 2 files, 268 lines
|
||||
**Languages:** C (268 lines)
|
||||
|
||||
## Version 0.11.0 - 2026-01-27
|
||||
|
||||
The system now processes buffered client data after an upstream connection closes, ensuring no data loss in connection scenarios. A corresponding test validates this behavior.
|
||||
|
||||
**Changes:** 2 files, 103 lines
|
||||
**Languages:** C (103 lines)
|
||||
|
||||
## Version 0.10.0 - 2026-01-06
|
||||
|
||||
update c, h files
|
||||
|
||||
**Changes:** 4 files, 145 lines
|
||||
**Languages:** C (145 lines)
|
||||
|
||||
## Version 0.9.0 - 2026-01-01
|
||||
|
||||
update c files
|
||||
|
||||
**Changes:** 1 files, 5 lines
|
||||
**Languages:** C (5 lines)
|
||||
|
||||
## Version 0.8.0 - 2025-12-29
|
||||
|
||||
Enables keep-alive connections for internal routes, allowing multiple requests over the same connection. Adds a test to verify handling of the second request on an internal route.
|
||||
|
||||
**Changes:** 2 files, 99 lines
|
||||
**Languages:** C (99 lines)
|
||||
|
||||
## Version 0.7.0 - 2025-12-29
|
||||
|
||||
The dashboard now formats numbers and times in metrics for improved readability.
|
||||
|
||||
**Changes:** 1 files, 14 lines
|
||||
**Languages:** C (14 lines)
|
||||
|
||||
## Version 0.6.0 - 2025-12-29
|
||||
|
||||
Integrates monitoring metrics into connection handling to provide real-time insights into network performance. Redesigns the dashboard with advanced metrics and charts for enhanced visualization of system data.
|
||||
|
||||
**Changes:** 9 files, 1245 lines
|
||||
**Languages:** C (1245 lines)
|
||||
|
||||
## Version 0.5.0 - 2025-12-28
|
||||
|
||||
Enhances authentication security by preventing timing attacks and clearing sensitive memory, while adding rate limiting to protect against abusive client requests. Enables SSL hostname verification and preferred cipher suites for improved connection security, and fixes request denial when rate limit allocation fails.
|
||||
|
||||
**Changes:** 49 files, 138 lines
|
||||
**Languages:** C (134 lines), Markdown (4 lines)
|
||||
|
||||
## Version 0.4.0 - 2025-12-15
|
||||
|
||||
Add comprehensive tests for the auth, buffer, and connection modules. Enhance the build process with logging test support and an increased minimum coverage threshold.
|
||||
|
||||
**Changes:** 8 files, 2046 lines
|
||||
**Languages:** C (2036 lines), Other (10 lines)
|
||||
|
||||
## Version 0.3.0 - 2025-12-15
|
||||
|
||||
Adds socket optimization functions that enable TCP_NODELAY for reduced connection latency and enhance upstream handling. Includes tests for connection optimizations, caching, and cleanup to verify functionality.
|
||||
|
||||
**Changes:** 2 files, 146 lines
|
||||
**Languages:** C (146 lines)
|
||||
|
||||
## Version 0.2.0 - 2025-12-15
|
||||
|
||||
Optimizes performance through enhanced socket settings and caching, reducing latency in connections. Adds host header validation to client requests and improves DNS resolution error handling for more reliable network operations.
|
||||
|
||||
**Changes:** 4 files, 79 lines
|
||||
**Languages:** C (79 lines)
|
||||
|
||||
## Version 0.1.0 - 2025-12-15
|
||||
|
||||
Implements zero-copy data forwarding to enhance connection performance. Caches patch buffers in connection structures and optimizes buffer compaction during read handling to reduce memory allocations.
|
||||
|
||||
**Changes:** 2 files, 153 lines
|
||||
**Languages:** C (153 lines)
|
||||
@@ -1,7 +1,5 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -Werror -O3 -march=native -flto=auto -fomit-frame-pointer -D_GNU_SOURCE
|
||||
CFLAGS = -Wall -Wextra -Werror -O3 -march=native -flto -fomit-frame-pointer -D_GNU_SOURCE
|
||||
CFLAGS_DEBUG = -Wall -Wextra -Werror -O0 -g -D_GNU_SOURCE
|
||||
CFLAGS_COV = -Wall -Wextra -Werror -O0 -g -D_GNU_SOURCE --coverage -fprofile-arcs -ftest-coverage
|
||||
LDFLAGS = -flto -lssl -lcrypto -lsqlite3 -lm -lpthread
|
||||
@@ -15,28 +13,15 @@ SOURCES = $(SRC_DIR)/main.c \
|
||||
$(SRC_DIR)/buffer.c \
|
||||
$(SRC_DIR)/logging.c \
|
||||
$(SRC_DIR)/config.c \
|
||||
$(SRC_DIR)/config_parser.c \
|
||||
$(SRC_DIR)/monitor.c \
|
||||
$(SRC_DIR)/http.c \
|
||||
$(SRC_DIR)/http_response.c \
|
||||
$(SRC_DIR)/ssl_handler.c \
|
||||
$(SRC_DIR)/connection.c \
|
||||
$(SRC_DIR)/client_handler.c \
|
||||
$(SRC_DIR)/upstream.c \
|
||||
$(SRC_DIR)/forwarding.c \
|
||||
$(SRC_DIR)/dashboard.c \
|
||||
$(SRC_DIR)/rate_limit.c \
|
||||
$(SRC_DIR)/auth.c \
|
||||
$(SRC_DIR)/base64.c \
|
||||
$(SRC_DIR)/health_check.c \
|
||||
$(SRC_DIR)/patch.c \
|
||||
$(SRC_DIR)/socket_utils.c \
|
||||
$(SRC_DIR)/epoll_utils.c \
|
||||
$(SRC_DIR)/time_utils.c \
|
||||
$(SRC_DIR)/histogram.c \
|
||||
$(SRC_DIR)/deque.c \
|
||||
$(SRC_DIR)/rate_tracker.c \
|
||||
$(SRC_DIR)/stats_collector.c \
|
||||
cJSON.c
|
||||
|
||||
OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(SOURCES)))
|
||||
@@ -57,36 +42,22 @@ TEST_SOURCES = $(TESTS_DIR)/test_main.c \
|
||||
$(TESTS_DIR)/test_dashboard.c \
|
||||
$(TESTS_DIR)/test_health_check.c \
|
||||
$(TESTS_DIR)/test_ssl_handler.c \
|
||||
$(TESTS_DIR)/test_connection.c \
|
||||
$(TESTS_DIR)/test_logging.c
|
||||
$(TESTS_DIR)/test_connection.c
|
||||
|
||||
TEST_OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(TEST_SOURCES)))
|
||||
|
||||
TEST_LIB_SOURCES = $(SRC_DIR)/buffer.c \
|
||||
$(SRC_DIR)/logging.c \
|
||||
$(SRC_DIR)/config.c \
|
||||
$(SRC_DIR)/config_parser.c \
|
||||
$(SRC_DIR)/monitor.c \
|
||||
$(SRC_DIR)/http.c \
|
||||
$(SRC_DIR)/http_response.c \
|
||||
$(SRC_DIR)/ssl_handler.c \
|
||||
$(SRC_DIR)/connection.c \
|
||||
$(SRC_DIR)/client_handler.c \
|
||||
$(SRC_DIR)/upstream.c \
|
||||
$(SRC_DIR)/forwarding.c \
|
||||
$(SRC_DIR)/dashboard.c \
|
||||
$(SRC_DIR)/rate_limit.c \
|
||||
$(SRC_DIR)/auth.c \
|
||||
$(SRC_DIR)/base64.c \
|
||||
$(SRC_DIR)/health_check.c \
|
||||
$(SRC_DIR)/patch.c \
|
||||
$(SRC_DIR)/socket_utils.c \
|
||||
$(SRC_DIR)/epoll_utils.c \
|
||||
$(SRC_DIR)/time_utils.c \
|
||||
$(SRC_DIR)/histogram.c \
|
||||
$(SRC_DIR)/deque.c \
|
||||
$(SRC_DIR)/rate_tracker.c \
|
||||
$(SRC_DIR)/stats_collector.c \
|
||||
cJSON.c
|
||||
|
||||
TEST_LIB_OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(TEST_LIB_SOURCES)))
|
||||
@@ -106,13 +77,91 @@ $(BUILD_DIR):
|
||||
$(TARGET): $(OBJECTS)
|
||||
$(CC) $(OBJECTS) -o $@ $(LDFLAGS)
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
|
||||
$(BUILD_DIR)/main.o: $(SRC_DIR)/main.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/cJSON.o: cJSON.c | $(BUILD_DIR)
|
||||
$(BUILD_DIR)/buffer.o: $(SRC_DIR)/buffer.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_%.o: $(TESTS_DIR)/test_%.c | $(BUILD_DIR)
|
||||
$(BUILD_DIR)/logging.o: $(SRC_DIR)/logging.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/config.o: $(SRC_DIR)/config.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/monitor.o: $(SRC_DIR)/monitor.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/http.o: $(SRC_DIR)/http.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/ssl_handler.o: $(SRC_DIR)/ssl_handler.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/connection.o: $(SRC_DIR)/connection.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/dashboard.o: $(SRC_DIR)/dashboard.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/rate_limit.o: $(SRC_DIR)/rate_limit.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/auth.o: $(SRC_DIR)/auth.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/health_check.o: $(SRC_DIR)/health_check.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/patch.o: $(SRC_DIR)/patch.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/cJSON.o: cJSON.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_main.o: $(TESTS_DIR)/test_main.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_http.o: $(TESTS_DIR)/test_http.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_buffer.o: $(TESTS_DIR)/test_buffer.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_config.o: $(TESTS_DIR)/test_config.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_routing.o: $(TESTS_DIR)/test_routing.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_host_rewrite.o: $(TESTS_DIR)/test_host_rewrite.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_http_helpers.o: $(TESTS_DIR)/test_http_helpers.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_patch.o: $(TESTS_DIR)/test_patch.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_auth.o: $(TESTS_DIR)/test_auth.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_rate_limit.o: $(TESTS_DIR)/test_rate_limit.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_monitor.o: $(TESTS_DIR)/test_monitor.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_dashboard.o: $(TESTS_DIR)/test_dashboard.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_health_check.o: $(TESTS_DIR)/test_health_check.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_ssl_handler.o: $(TESTS_DIR)/test_ssl_handler.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD_DIR)/test_connection.o: $(TESTS_DIR)/test_connection.c
|
||||
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
|
||||
|
||||
$(TEST_TARGET): $(BUILD_DIR) $(TEST_OBJECTS) $(TEST_LIB_OBJECTS)
|
||||
@@ -129,14 +178,34 @@ run: $(TARGET)
|
||||
|
||||
coverage: clean
|
||||
mkdir -p $(BUILD_DIR)
|
||||
@for src in $(TEST_LIB_SOURCES); do \
|
||||
obj=$(BUILD_DIR)/$$(basename $${src%.c}.o); \
|
||||
$(CC) $(CFLAGS_COV) -c $$src -o $$obj; \
|
||||
done
|
||||
@for src in $(TEST_SOURCES); do \
|
||||
obj=$(BUILD_DIR)/$$(basename $${src%.c}.o); \
|
||||
$(CC) $(CFLAGS_COV) -I$(SRC_DIR) -c $$src -o $$obj; \
|
||||
done
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_main.c -o build/test_main.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_http.c -o build/test_http.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_buffer.c -o build/test_buffer.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_config.c -o build/test_config.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_routing.c -o build/test_routing.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_host_rewrite.c -o build/test_host_rewrite.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_http_helpers.c -o build/test_http_helpers.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_patch.c -o build/test_patch.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_auth.c -o build/test_auth.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_rate_limit.c -o build/test_rate_limit.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_monitor.c -o build/test_monitor.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_dashboard.c -o build/test_dashboard.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_health_check.c -o build/test_health_check.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_ssl_handler.c -o build/test_ssl_handler.o
|
||||
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_connection.c -o build/test_connection.o
|
||||
$(CC) $(CFLAGS_COV) -c src/buffer.c -o build/buffer.o
|
||||
$(CC) $(CFLAGS_COV) -c src/logging.c -o build/logging.o
|
||||
$(CC) $(CFLAGS_COV) -c src/config.c -o build/config.o
|
||||
$(CC) $(CFLAGS_COV) -c src/monitor.c -o build/monitor.o
|
||||
$(CC) $(CFLAGS_COV) -c src/http.c -o build/http.o
|
||||
$(CC) $(CFLAGS_COV) -c src/ssl_handler.c -o build/ssl_handler.o
|
||||
$(CC) $(CFLAGS_COV) -c src/connection.c -o build/connection.o
|
||||
$(CC) $(CFLAGS_COV) -c src/dashboard.c -o build/dashboard.o
|
||||
$(CC) $(CFLAGS_COV) -c src/rate_limit.c -o build/rate_limit.o
|
||||
$(CC) $(CFLAGS_COV) -c src/auth.c -o build/auth.o
|
||||
$(CC) $(CFLAGS_COV) -c src/health_check.c -o build/health_check.o
|
||||
$(CC) $(CFLAGS_COV) -c src/patch.c -o build/patch.o
|
||||
$(CC) $(CFLAGS_COV) -c cJSON.c -o build/cJSON.o
|
||||
$(CC) $(TEST_OBJECTS) $(TEST_LIB_OBJECTS) -o $(TEST_TARGET) $(LDFLAGS_COV)
|
||||
./$(TEST_TARGET)
|
||||
@echo ""
|
||||
@@ -192,14 +261,34 @@ coverage-html: coverage
|
||||
|
||||
valgrind: clean
|
||||
mkdir -p $(BUILD_DIR)
|
||||
@for src in $(TEST_LIB_SOURCES); do \
|
||||
obj=$(BUILD_DIR)/$$(basename $${src%.c}.o); \
|
||||
$(CC) $(CFLAGS_DEBUG) -c $$src -o $$obj; \
|
||||
done
|
||||
@for src in $(TEST_SOURCES); do \
|
||||
obj=$(BUILD_DIR)/$$(basename $${src%.c}.o); \
|
||||
$(CC) $(CFLAGS_DEBUG) -I$(SRC_DIR) -c $$src -o $$obj; \
|
||||
done
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_main.c -o build/test_main.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_http.c -o build/test_http.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_buffer.c -o build/test_buffer.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_config.c -o build/test_config.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_routing.c -o build/test_routing.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_host_rewrite.c -o build/test_host_rewrite.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_http_helpers.c -o build/test_http_helpers.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_patch.c -o build/test_patch.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_auth.c -o build/test_auth.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_rate_limit.c -o build/test_rate_limit.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_monitor.c -o build/test_monitor.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_dashboard.c -o build/test_dashboard.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_health_check.c -o build/test_health_check.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_ssl_handler.c -o build/test_ssl_handler.o
|
||||
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_connection.c -o build/test_connection.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/buffer.c -o build/buffer.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/logging.c -o build/logging.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/config.c -o build/config.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/monitor.c -o build/monitor.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/http.c -o build/http.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/ssl_handler.c -o build/ssl_handler.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/connection.c -o build/connection.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/dashboard.c -o build/dashboard.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/rate_limit.c -o build/rate_limit.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/auth.c -o build/auth.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/health_check.c -o build/health_check.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c src/patch.c -o build/patch.o
|
||||
$(CC) $(CFLAGS_DEBUG) -c cJSON.c -o build/cJSON.o
|
||||
$(CC) $(TEST_OBJECTS) $(TEST_LIB_OBJECTS) -o $(TEST_TARGET) -lssl -lcrypto -lsqlite3 -lm -lpthread
|
||||
valgrind --leak-check=full --show-leak-kinds=definite,indirect --error-exitcode=1 ./$(TEST_TARGET) 2>&1 | tee valgrind.log; \
|
||||
VALGRIND_EXIT=$$?; \
|
||||
|
||||
@@ -44,25 +44,11 @@ Compiles the source files in `src/` and produces the `rproxy` executable.
|
||||
|
||||
```bash
|
||||
make test # Run unit tests
|
||||
make coverage # Run tests with coverage report (minimum 69% required)
|
||||
make coverage # Run tests with coverage report (minimum 60% required)
|
||||
make coverage-html # Generate HTML coverage report
|
||||
make valgrind # Run tests with memory leak detection
|
||||
```
|
||||
|
||||
### Test Results
|
||||
|
||||
```
|
||||
Test Results: 741/741 passed
|
||||
|
||||
HEAP SUMMARY:
|
||||
in use at exit: 0 bytes in 0 blocks
|
||||
total heap usage: 155,794 allocs, 155,794 frees, 13,900,573 bytes allocated
|
||||
|
||||
All heap blocks were freed -- no leaks are possible
|
||||
|
||||
ERROR SUMMARY: 0 errors from 0 contexts
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is defined in `proxy_config.json`:
|
||||
|
||||
+95
-69
@@ -1,22 +1,14 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "auth.h"
|
||||
#include "base64.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
static char g_dashboard_username[128] = "";
|
||||
static char g_dashboard_password_hash[256] = "";
|
||||
static int g_auth_enabled = 0;
|
||||
|
||||
static int constant_time_compare(const char *a, const char *b, size_t len) {
|
||||
return CRYPTO_memcmp(a, b, len) == 0;
|
||||
}
|
||||
|
||||
static void compute_sha256(const char *input, char *output, size_t output_size) {
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
if (!ctx) return;
|
||||
@@ -56,28 +48,52 @@ int auth_check_credentials(const char *username, const char *password) {
|
||||
if (!g_auth_enabled) return 1;
|
||||
if (!username || !password) return 0;
|
||||
|
||||
if (strcmp(username, g_dashboard_username) != 0) return 0;
|
||||
|
||||
char password_hash[256];
|
||||
compute_sha256(password, password_hash, sizeof(password_hash));
|
||||
|
||||
size_t username_len = strlen(username);
|
||||
size_t expected_username_len = strlen(g_dashboard_username);
|
||||
int username_match = (username_len == expected_username_len) &&
|
||||
constant_time_compare(username, g_dashboard_username, username_len);
|
||||
|
||||
int hash_match = constant_time_compare(password_hash, g_dashboard_password_hash, 64);
|
||||
|
||||
memset(password_hash, 0, sizeof(password_hash));
|
||||
|
||||
return username_match && hash_match;
|
||||
return strcmp(password_hash, g_dashboard_password_hash) == 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *expected_username;
|
||||
const char *expected_password_hash;
|
||||
} auth_credentials_t;
|
||||
static int base64_decode_char(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int auth_parse_and_verify(const char *auth_header, const auth_credentials_t *creds,
|
||||
char *error_msg, size_t error_size) {
|
||||
static int base64_decode(const char *input, char *output, size_t output_size) {
|
||||
size_t input_len = strlen(input);
|
||||
size_t output_idx = 0;
|
||||
|
||||
for (size_t i = 0; i < input_len && output_idx < output_size - 1; i += 4) {
|
||||
int v[4] = {0, 0, 0, 0};
|
||||
int pad = 0;
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (i + j >= input_len || input[i + j] == '=') {
|
||||
pad++;
|
||||
v[j] = 0;
|
||||
} else {
|
||||
v[j] = base64_decode_char(input[i + j]);
|
||||
if (v[j] < 0) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (output_idx < output_size - 1) output[output_idx++] = (v[0] << 2) | (v[1] >> 4);
|
||||
if (pad < 2 && output_idx < output_size - 1) output[output_idx++] = (v[1] << 4) | (v[2] >> 2);
|
||||
if (pad < 1 && output_idx < output_size - 1) output[output_idx++] = (v[2] << 6) | v[3];
|
||||
}
|
||||
|
||||
output[output_idx] = '\0';
|
||||
return output_idx;
|
||||
}
|
||||
|
||||
int auth_check_basic_auth(const char *auth_header, char *error_msg, size_t error_size) {
|
||||
if (!g_auth_enabled) return 1;
|
||||
if (!auth_header) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Authentication required", error_size - 1);
|
||||
@@ -92,18 +108,8 @@ static int auth_parse_and_verify(const char *auth_header, const auth_credentials
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *encoded = auth_header + 6;
|
||||
size_t encoded_len = strlen(encoded);
|
||||
if (encoded_len > 680) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Credentials too long", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char decoded[512];
|
||||
int decoded_len = base64_decode(encoded, decoded, sizeof(decoded));
|
||||
if (decoded_len < 0) {
|
||||
if (base64_decode(auth_header + 6, decoded, sizeof(decoded)) < 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
@@ -112,7 +118,6 @@ static int auth_parse_and_verify(const char *auth_header, const auth_credentials
|
||||
|
||||
char *colon = strchr(decoded, ':');
|
||||
if (!colon) {
|
||||
memset(decoded, 0, sizeof(decoded));
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
@@ -123,20 +128,7 @@ static int auth_parse_and_verify(const char *auth_header, const auth_credentials
|
||||
const char *username = decoded;
|
||||
const char *password = colon + 1;
|
||||
|
||||
char password_hash[256];
|
||||
compute_sha256(password, password_hash, sizeof(password_hash));
|
||||
|
||||
size_t username_len = strlen(username);
|
||||
size_t expected_username_len = strlen(creds->expected_username);
|
||||
int username_match = (username_len == expected_username_len) &&
|
||||
constant_time_compare(username, creds->expected_username, username_len);
|
||||
|
||||
int hash_match = constant_time_compare(password_hash, creds->expected_password_hash, 64);
|
||||
|
||||
memset(decoded, 0, sizeof(decoded));
|
||||
memset(password_hash, 0, sizeof(password_hash));
|
||||
|
||||
if (!username_match || !hash_match) {
|
||||
if (!auth_check_credentials(username, password)) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid username or password", error_size - 1);
|
||||
}
|
||||
@@ -146,25 +138,59 @@ static int auth_parse_and_verify(const char *auth_header, const auth_credentials
|
||||
return 1;
|
||||
}
|
||||
|
||||
int auth_check_basic_auth(const char *auth_header, char *error_msg, size_t error_size) {
|
||||
if (!g_auth_enabled) return 1;
|
||||
|
||||
auth_credentials_t creds = {
|
||||
.expected_username = g_dashboard_username,
|
||||
.expected_password_hash = g_dashboard_password_hash
|
||||
};
|
||||
|
||||
return auth_parse_and_verify(auth_header, &creds, error_msg, error_size);
|
||||
}
|
||||
|
||||
int auth_check_route_basic_auth(const route_config_t *route, const char *auth_header,
|
||||
char *error_msg, size_t error_size) {
|
||||
int auth_check_route_basic_auth(const route_config_t *route, const char *auth_header, char *error_msg, size_t error_size) {
|
||||
if (!route || !route->use_auth) return 1;
|
||||
|
||||
auth_credentials_t creds = {
|
||||
.expected_username = route->username,
|
||||
.expected_password_hash = route->password_hash
|
||||
};
|
||||
if (!auth_header) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Authentication required", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return auth_parse_and_verify(auth_header, &creds, error_msg, error_size);
|
||||
if (strncmp(auth_header, "Basic ", 6) != 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid authentication method", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char decoded[512];
|
||||
if (base64_decode(auth_header + 6, decoded, sizeof(decoded)) < 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *colon = strchr(decoded, ':');
|
||||
if (!colon) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
*colon = '\0';
|
||||
const char *username = decoded;
|
||||
const char *password = colon + 1;
|
||||
|
||||
if (strcmp(username, route->username) != 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid username or password", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char password_hash[256];
|
||||
compute_sha256(password, password_hash, sizeof(password_hash));
|
||||
|
||||
if (strcmp(password_hash, route->password_hash) != 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid username or password", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
Executable → Regular
@@ -1,235 +0,0 @@
|
||||
#include "auth.h"
|
||||
#include "logging.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
static char g_dashboard_username[128] = "";
|
||||
static char g_dashboard_password_hash[256] = "";
|
||||
static int g_auth_enabled = 0;
|
||||
|
||||
static int constant_time_compare(const char *a, const char *b, size_t len) {
|
||||
return CRYPTO_memcmp(a, b, len) == 0;
|
||||
}
|
||||
|
||||
static void compute_sha256(const char *input, char *output, size_t output_size) {
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
if (!ctx) return;
|
||||
|
||||
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len = 0;
|
||||
|
||||
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
|
||||
EVP_DigestUpdate(ctx, input, strlen(input));
|
||||
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
for (unsigned int i = 0; i < hash_len && (i * 2 + 2) < output_size; i++) {
|
||||
snprintf(output + (i * 2), 3, "%02x", hash[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void auth_init(const char *username, const char *password) {
|
||||
if (!username || !password || strlen(username) == 0 || strlen(password) == 0) {
|
||||
g_auth_enabled = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(g_dashboard_username, username, sizeof(g_dashboard_username) - 1);
|
||||
g_dashboard_username[sizeof(g_dashboard_username) - 1] = '\0';
|
||||
|
||||
compute_sha256(password, g_dashboard_password_hash, sizeof(g_dashboard_password_hash));
|
||||
g_auth_enabled = 1;
|
||||
log_info("Dashboard authentication enabled for user: %s", username);
|
||||
}
|
||||
|
||||
int auth_is_enabled(void) {
|
||||
return g_auth_enabled;
|
||||
}
|
||||
|
||||
int auth_check_credentials(const char *username, const char *password) {
|
||||
if (!g_auth_enabled) return 1;
|
||||
if (!username || !password) return 0;
|
||||
|
||||
char password_hash[256];
|
||||
compute_sha256(password, password_hash, sizeof(password_hash));
|
||||
|
||||
size_t username_len = strlen(username);
|
||||
size_t expected_username_len = strlen(g_dashboard_username);
|
||||
int username_match = (username_len == expected_username_len) &&
|
||||
constant_time_compare(username, g_dashboard_username, username_len);
|
||||
|
||||
int hash_match = constant_time_compare(password_hash, g_dashboard_password_hash, 64);
|
||||
|
||||
memset(password_hash, 0, sizeof(password_hash));
|
||||
|
||||
return username_match && hash_match;
|
||||
}
|
||||
|
||||
static int base64_decode_char(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int base64_decode(const char *input, char *output, size_t output_size) {
|
||||
size_t input_len = strlen(input);
|
||||
size_t output_idx = 0;
|
||||
|
||||
for (size_t i = 0; i < input_len && output_idx < output_size - 1; i += 4) {
|
||||
int v[4] = {0, 0, 0, 0};
|
||||
int pad = 0;
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (i + j >= input_len || input[i + j] == '=') {
|
||||
pad++;
|
||||
v[j] = 0;
|
||||
} else {
|
||||
v[j] = base64_decode_char(input[i + j]);
|
||||
if (v[j] < 0) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (output_idx < output_size - 1) output[output_idx++] = (v[0] << 2) | (v[1] >> 4);
|
||||
if (pad < 2 && output_idx < output_size - 1) output[output_idx++] = (v[1] << 4) | (v[2] >> 2);
|
||||
if (pad < 1 && output_idx < output_size - 1) output[output_idx++] = (v[2] << 6) | v[3];
|
||||
}
|
||||
|
||||
output[output_idx] = '\0';
|
||||
return output_idx;
|
||||
}
|
||||
|
||||
int auth_check_basic_auth(const char *auth_header, char *error_msg, size_t error_size) {
|
||||
if (!g_auth_enabled) return 1;
|
||||
if (!auth_header) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Authentication required", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strncmp(auth_header, "Basic ", 6) != 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid authentication method", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *encoded = auth_header + 6;
|
||||
size_t encoded_len = strlen(encoded);
|
||||
if (encoded_len > 680) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Credentials too long", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char decoded[512];
|
||||
int decoded_len = base64_decode(encoded, decoded, sizeof(decoded));
|
||||
if (decoded_len < 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *colon = strchr(decoded, ':');
|
||||
if (!colon) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
*colon = '\0';
|
||||
const char *username = decoded;
|
||||
const char *password = colon + 1;
|
||||
|
||||
int result = auth_check_credentials(username, password);
|
||||
memset(decoded, 0, sizeof(decoded));
|
||||
|
||||
if (!result) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid username or password", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int auth_check_route_basic_auth(const route_config_t *route, const char *auth_header, char *error_msg, size_t error_size) {
|
||||
if (!route || !route->use_auth) return 1;
|
||||
|
||||
if (!auth_header) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Authentication required", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strncmp(auth_header, "Basic ", 6) != 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid authentication method", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *encoded = auth_header + 6;
|
||||
size_t encoded_len = strlen(encoded);
|
||||
if (encoded_len > 680) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Credentials too long", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char decoded[512];
|
||||
int decoded_len = base64_decode(encoded, decoded, sizeof(decoded));
|
||||
if (decoded_len < 0) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *colon = strchr(decoded, ':');
|
||||
if (!colon) {
|
||||
memset(decoded, 0, sizeof(decoded));
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid credentials format", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
*colon = '\0';
|
||||
const char *username = decoded;
|
||||
const char *password = colon + 1;
|
||||
|
||||
char password_hash[256];
|
||||
compute_sha256(password, password_hash, sizeof(password_hash));
|
||||
|
||||
size_t username_len = strlen(username);
|
||||
size_t expected_username_len = strlen(route->username);
|
||||
int username_match = (username_len == expected_username_len) &&
|
||||
constant_time_compare(username, route->username, username_len);
|
||||
|
||||
int hash_match = constant_time_compare(password_hash, route->password_hash, 64);
|
||||
|
||||
memset(decoded, 0, sizeof(decoded));
|
||||
memset(password_hash, 0, sizeof(password_hash));
|
||||
|
||||
if (!username_match || !hash_match) {
|
||||
if (error_msg && error_size > 0) {
|
||||
strncpy(error_msg, "Invalid username or password", error_size - 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
#include "config.h"
|
||||
#include "logging.h"
|
||||
#include "../cJSON.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
static time_t config_file_mtime = 0;
|
||||
|
||||
static void compute_password_hash(const char *password, char *output, size_t output_size) {
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
if (!ctx) return;
|
||||
|
||||
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len = 0;
|
||||
|
||||
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
|
||||
EVP_DigestUpdate(ctx, password, strlen(password));
|
||||
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
for (unsigned int i = 0; i < hash_len && (i * 2 + 2) < output_size; i++) {
|
||||
snprintf(output + (i * 2), 3, "%02x", hash[i]);
|
||||
}
|
||||
}
|
||||
|
||||
app_config_t *config = NULL;
|
||||
static app_config_t *stale_configs_head = NULL;
|
||||
|
||||
static int is_valid_hostname(const char *hostname) {
|
||||
if (!hostname || strlen(hostname) == 0 || strlen(hostname) > 253) return 0;
|
||||
|
||||
const char *p = hostname;
|
||||
int label_len = 0;
|
||||
|
||||
while (*p) {
|
||||
char c = *p;
|
||||
if (c == '.') {
|
||||
if (label_len == 0) return 0;
|
||||
label_len = 0;
|
||||
} else if (isalnum((unsigned char)c) || c == '-' || c == '_') {
|
||||
label_len++;
|
||||
if (label_len > 63) return 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_valid_ip(const char *ip) {
|
||||
if (!ip) return 0;
|
||||
int dots = 0;
|
||||
int num = 0;
|
||||
int has_digit = 0;
|
||||
|
||||
while (*ip) {
|
||||
if (*ip == '.') {
|
||||
if (!has_digit || num > 255) return 0;
|
||||
dots++;
|
||||
num = 0;
|
||||
has_digit = 0;
|
||||
} else if (isdigit((unsigned char)*ip)) {
|
||||
num = num * 10 + (*ip - '0');
|
||||
has_digit = 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
ip++;
|
||||
}
|
||||
|
||||
return dots == 3 && has_digit && num <= 255;
|
||||
}
|
||||
|
||||
static int is_valid_host(const char *host) {
|
||||
return is_valid_hostname(host) || is_valid_ip(host);
|
||||
}
|
||||
|
||||
static char* read_file_to_string(const char *filename) {
|
||||
FILE *f = fopen(filename, "rb");
|
||||
if (!f) return NULL;
|
||||
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
long length = ftell(f);
|
||||
if (length < 0 || length > 1024*1024) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f, 0, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
char *buffer = malloc((size_t)length + 1);
|
||||
if (!buffer) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
size_t read_len = fread(buffer, 1, (size_t)length, f);
|
||||
buffer[read_len] = '\0';
|
||||
fclose(f);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int config_load(const char *filename) {
|
||||
log_info("Loading configuration from %s", filename);
|
||||
char *json_string = read_file_to_string(filename);
|
||||
if (!json_string) {
|
||||
log_error("Could not read config file");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_Parse(json_string);
|
||||
if (!root) {
|
||||
const char *error_ptr = cJSON_GetErrorPtr();
|
||||
fprintf(stderr, "JSON parse error: %s\n", error_ptr ? error_ptr : "unknown");
|
||||
free(json_string);
|
||||
return 0;
|
||||
}
|
||||
free(json_string);
|
||||
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
if (!new_config) {
|
||||
log_error("Failed to allocate memory for new config");
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
new_config->ref_count = 1; // Start with one reference for the global 'config' pointer
|
||||
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
new_config->port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
|
||||
if (new_config->port < 1 || new_config->port > 65535) {
|
||||
fprintf(stderr, "Invalid port number: %d\n", new_config->port);
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *proxy_array = cJSON_GetObjectItem(root, "reverse_proxy");
|
||||
if (cJSON_IsArray(proxy_array)) {
|
||||
new_config->route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (new_config->route_count <= 0) {
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
new_config->routes = calloc(new_config->route_count, sizeof(route_config_t));
|
||||
if (!new_config->routes) {
|
||||
log_error("Failed to allocate memory for routes");
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
cJSON *route_item;
|
||||
cJSON_ArrayForEach(route_item, proxy_array) {
|
||||
route_config_t *route = &new_config->routes[i];
|
||||
|
||||
cJSON *hostname = cJSON_GetObjectItem(route_item, "hostname");
|
||||
cJSON *upstream_host = cJSON_GetObjectItem(route_item, "upstream_host");
|
||||
cJSON *upstream_port = cJSON_GetObjectItem(route_item, "upstream_port");
|
||||
|
||||
if (!cJSON_IsString(hostname) || !cJSON_IsString(upstream_host) || !cJSON_IsNumber(upstream_port)) {
|
||||
fprintf(stderr, "Invalid route configuration at index %d\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_valid_host(hostname->valuestring)) {
|
||||
fprintf(stderr, "Invalid hostname at index %d: %s\n", i, hostname->valuestring);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_valid_host(upstream_host->valuestring)) {
|
||||
fprintf(stderr, "Invalid upstream_host at index %d: %s\n", i, upstream_host->valuestring);
|
||||
continue;
|
||||
}
|
||||
|
||||
strncpy(route->hostname, hostname->valuestring, sizeof(route->hostname) - 1);
|
||||
route->hostname[sizeof(route->hostname) - 1] = '\0';
|
||||
strncpy(route->upstream_host, upstream_host->valuestring, sizeof(route->upstream_host) - 1);
|
||||
route->upstream_host[sizeof(route->upstream_host) - 1] = '\0';
|
||||
route->upstream_port = upstream_port->valueint;
|
||||
|
||||
if (route->upstream_port < 1 || route->upstream_port > 65535) {
|
||||
fprintf(stderr, "Invalid upstream port for %s: %d\n", route->hostname, route->upstream_port);
|
||||
continue;
|
||||
}
|
||||
|
||||
route->use_ssl = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "use_ssl"));
|
||||
route->rewrite_host = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "rewrite_host"));
|
||||
|
||||
route->use_auth = 0;
|
||||
route->username[0] = '\0';
|
||||
route->password_hash[0] = '\0';
|
||||
|
||||
cJSON *use_auth = cJSON_GetObjectItem(route_item, "use_auth");
|
||||
cJSON *auth_username = cJSON_GetObjectItem(route_item, "username");
|
||||
cJSON *auth_password = cJSON_GetObjectItem(route_item, "password");
|
||||
|
||||
if (cJSON_IsTrue(use_auth) && cJSON_IsString(auth_username) && cJSON_IsString(auth_password)) {
|
||||
if (strlen(auth_username->valuestring) > 0 && strlen(auth_password->valuestring) > 0) {
|
||||
route->use_auth = 1;
|
||||
strncpy(route->username, auth_username->valuestring, sizeof(route->username) - 1);
|
||||
route->username[sizeof(route->username) - 1] = '\0';
|
||||
compute_password_hash(auth_password->valuestring, route->password_hash, sizeof(route->password_hash));
|
||||
}
|
||||
}
|
||||
|
||||
route->patches.rule_count = 0;
|
||||
cJSON *patch_obj = cJSON_GetObjectItem(route_item, "patch");
|
||||
if (cJSON_IsObject(patch_obj)) {
|
||||
cJSON *patch_item = NULL;
|
||||
cJSON_ArrayForEach(patch_item, patch_obj) {
|
||||
if (route->patches.rule_count >= MAX_PATCH_RULES) {
|
||||
log_info("Maximum patch rules reached for %s", route->hostname);
|
||||
break;
|
||||
}
|
||||
if (!patch_item->string) continue;
|
||||
size_t key_len = strlen(patch_item->string);
|
||||
if (key_len == 0 || key_len >= MAX_PATCH_KEY_SIZE) continue;
|
||||
|
||||
patch_rule_t *rule = &route->patches.rules[route->patches.rule_count];
|
||||
strncpy(rule->key, patch_item->string, MAX_PATCH_KEY_SIZE - 1);
|
||||
rule->key[MAX_PATCH_KEY_SIZE - 1] = '\0';
|
||||
rule->key_len = key_len;
|
||||
|
||||
if (cJSON_IsNull(patch_item)) {
|
||||
rule->is_null = 1;
|
||||
rule->value[0] = '\0';
|
||||
rule->value_len = 0;
|
||||
} else if (cJSON_IsString(patch_item)) {
|
||||
rule->is_null = 0;
|
||||
size_t val_len = strlen(patch_item->valuestring);
|
||||
if (val_len >= MAX_PATCH_VALUE_SIZE) val_len = MAX_PATCH_VALUE_SIZE - 1;
|
||||
strncpy(rule->value, patch_item->valuestring, MAX_PATCH_VALUE_SIZE - 1);
|
||||
rule->value[MAX_PATCH_VALUE_SIZE - 1] = '\0';
|
||||
rule->value_len = val_len;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
route->patches.rule_count++;
|
||||
}
|
||||
if (route->patches.rule_count > 0) {
|
||||
log_info("Loaded %d patch rules for %s", route->patches.rule_count, route->hostname);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("Route configured: %s -> %s:%d (SSL: %s, Rewrite Host: %s, Auth: %s)",
|
||||
route->hostname, route->upstream_host, route->upstream_port,
|
||||
route->use_ssl ? "yes" : "no", route->rewrite_host ? "yes" : "no",
|
||||
route->use_auth ? "yes" : "no");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
}
|
||||
config = new_config;
|
||||
|
||||
log_info("Loaded %d routes from %s", config->route_count, filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void config_ref_inc(app_config_t *conf) {
|
||||
if (conf) {
|
||||
atomic_fetch_add(&conf->ref_count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void config_ref_dec(app_config_t *conf) {
|
||||
if (!conf) return;
|
||||
|
||||
if (atomic_fetch_sub(&conf->ref_count, 1) == 1) {
|
||||
log_debug("Freeing configuration with port %d", conf->port);
|
||||
if (conf->routes) {
|
||||
free(conf->routes);
|
||||
}
|
||||
free(conf);
|
||||
}
|
||||
}
|
||||
|
||||
void config_free(void) {
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
config = NULL;
|
||||
}
|
||||
|
||||
app_config_t *current = stale_configs_head;
|
||||
while (current) {
|
||||
app_config_t *next = current->next;
|
||||
config_ref_dec(current);
|
||||
current = next;
|
||||
}
|
||||
stale_configs_head = NULL;
|
||||
}
|
||||
|
||||
void config_create_default(const char *filename) {
|
||||
FILE *f = fopen(filename, "r");
|
||||
if (f) {
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
|
||||
f = fopen(filename, "w");
|
||||
if (!f) {
|
||||
log_error("Cannot create default config file");
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(f, "{\n"
|
||||
" \"port\": 8080,\n"
|
||||
" \"reverse_proxy\": [\n"
|
||||
" {\n"
|
||||
" \"hostname\": \"localhost\",\n"
|
||||
" \"upstream_host\": \"127.0.0.1\",\n"
|
||||
" \"upstream_port\": 3000,\n"
|
||||
" \"use_ssl\": false,\n"
|
||||
" \"rewrite_host\": true\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"hostname\": \"example.com\",\n"
|
||||
" \"upstream_host\": \"127.0.0.1\",\n"
|
||||
" \"upstream_port\": 5000,\n"
|
||||
" \"use_ssl\": false,\n"
|
||||
" \"rewrite_host\": false\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n");
|
||||
fclose(f);
|
||||
log_info("Created default config file: %s", filename);
|
||||
}
|
||||
|
||||
route_config_t *config_find_route(const char *hostname) {
|
||||
if (!hostname) return NULL;
|
||||
|
||||
app_config_t *current_config = config;
|
||||
if (!current_config) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < current_config->route_count; i++) {
|
||||
if (strcasecmp(hostname, current_config->routes[i].hostname) == 0) {
|
||||
return ¤t_config->routes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int config_check_file_changed(const char *filename) {
|
||||
struct stat st;
|
||||
if (stat(filename, &st) != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (config_file_mtime == 0) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
return 0;
|
||||
}
|
||||
if (st.st_mtime != config_file_mtime) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_hot_reload(const char *filename) {
|
||||
log_info("Hot-reloading configuration from %s", filename);
|
||||
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
if (!new_config) {
|
||||
log_error("Hot-reload: Failed to allocate memory for new config");
|
||||
return 0;
|
||||
}
|
||||
new_config->ref_count = 1;
|
||||
|
||||
char *json_string = read_file_to_string(filename);
|
||||
if (!json_string) {
|
||||
log_error("Hot-reload: Could not read config file");
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_Parse(json_string);
|
||||
if (!root) {
|
||||
const char *error_ptr = cJSON_GetErrorPtr();
|
||||
log_error("Hot-reload: JSON parse error: %s", error_ptr ? error_ptr : "unknown");
|
||||
free(json_string);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
free(json_string);
|
||||
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
new_config->port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
|
||||
if (new_config->port < 1 || new_config->port > 65535) {
|
||||
log_error("Hot-reload: Invalid port number: %d", new_config->port);
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *proxy_array = cJSON_GetObjectItem(root, "reverse_proxy");
|
||||
if (cJSON_IsArray(proxy_array)) {
|
||||
new_config->route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (new_config->route_count <= 0) {
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
new_config->routes = calloc(new_config->route_count, sizeof(route_config_t));
|
||||
if (!new_config->routes) {
|
||||
log_error("Hot-reload: Failed to allocate memory for routes");
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
cJSON *route_item;
|
||||
cJSON_ArrayForEach(route_item, proxy_array) {
|
||||
route_config_t *route = &new_config->routes[i];
|
||||
|
||||
cJSON *hostname = cJSON_GetObjectItem(route_item, "hostname");
|
||||
cJSON *upstream_host = cJSON_GetObjectItem(route_item, "upstream_host");
|
||||
cJSON *upstream_port = cJSON_GetObjectItem(route_item, "upstream_port");
|
||||
|
||||
if (!cJSON_IsString(hostname) || !cJSON_IsString(upstream_host) || !cJSON_IsNumber(upstream_port)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_valid_host(hostname->valuestring) || !is_valid_host(upstream_host->valuestring)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
strncpy(route->hostname, hostname->valuestring, sizeof(route->hostname) - 1);
|
||||
route->hostname[sizeof(route->hostname) - 1] = '\0';
|
||||
strncpy(route->upstream_host, upstream_host->valuestring, sizeof(route->upstream_host) - 1);
|
||||
route->upstream_host[sizeof(route->upstream_host) - 1] = '\0';
|
||||
route->upstream_port = upstream_port->valueint;
|
||||
|
||||
if (route->upstream_port < 1 || route->upstream_port > 65535) {
|
||||
continue;
|
||||
}
|
||||
|
||||
route->use_ssl = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "use_ssl"));
|
||||
route->rewrite_host = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "rewrite_host"));
|
||||
|
||||
route->use_auth = 0;
|
||||
route->username[0] = '\0';
|
||||
route->password_hash[0] = '\0';
|
||||
|
||||
cJSON *use_auth = cJSON_GetObjectItem(route_item, "use_auth");
|
||||
cJSON *auth_username = cJSON_GetObjectItem(route_item, "username");
|
||||
cJSON *auth_password = cJSON_GetObjectItem(route_item, "password");
|
||||
|
||||
if (cJSON_IsTrue(use_auth) && cJSON_IsString(auth_username) && cJSON_IsString(auth_password)) {
|
||||
if (strlen(auth_username->valuestring) > 0 && strlen(auth_password->valuestring) > 0) {
|
||||
route->use_auth = 1;
|
||||
strncpy(route->username, auth_username->valuestring, sizeof(route->username) - 1);
|
||||
route->username[sizeof(route->username) - 1] = '\0';
|
||||
compute_password_hash(auth_password->valuestring, route->password_hash, sizeof(route->password_hash));
|
||||
}
|
||||
}
|
||||
|
||||
route->patches.rule_count = 0;
|
||||
cJSON *patch_obj = cJSON_GetObjectItem(route_item, "patch");
|
||||
if (cJSON_IsObject(patch_obj)) {
|
||||
cJSON *patch_item = NULL;
|
||||
cJSON_ArrayForEach(patch_item, patch_obj) {
|
||||
if (route->patches.rule_count >= MAX_PATCH_RULES) break;
|
||||
if (!patch_item->string) continue;
|
||||
size_t key_len = strlen(patch_item->string);
|
||||
if (key_len == 0 || key_len >= MAX_PATCH_KEY_SIZE) continue;
|
||||
|
||||
patch_rule_t *rule = &route->patches.rules[route->patches.rule_count];
|
||||
strncpy(rule->key, patch_item->string, MAX_PATCH_KEY_SIZE - 1);
|
||||
rule->key[MAX_PATCH_KEY_SIZE - 1] = '\0';
|
||||
rule->key_len = key_len;
|
||||
|
||||
if (cJSON_IsNull(patch_item)) {
|
||||
rule->is_null = 1;
|
||||
rule->value[0] = '\0';
|
||||
rule->value_len = 0;
|
||||
} else if (cJSON_IsString(patch_item)) {
|
||||
rule->is_null = 0;
|
||||
size_t val_len = strlen(patch_item->valuestring);
|
||||
if (val_len >= MAX_PATCH_VALUE_SIZE) val_len = MAX_PATCH_VALUE_SIZE - 1;
|
||||
strncpy(rule->value, patch_item->valuestring, MAX_PATCH_VALUE_SIZE - 1);
|
||||
rule->value[MAX_PATCH_VALUE_SIZE - 1] = '\0';
|
||||
rule->value_len = val_len;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
route->patches.rule_count++;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("Hot-reload route: %s -> %s:%d (SSL: %s, Auth: %s, Patches: %d)",
|
||||
route->hostname, route->upstream_host, route->upstream_port,
|
||||
route->use_ssl ? "yes" : "no", route->use_auth ? "yes" : "no",
|
||||
route->patches.rule_count);
|
||||
i++;
|
||||
}
|
||||
new_config->route_count = i;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
app_config_t *old_config = config;
|
||||
config = new_config;
|
||||
|
||||
if (old_config) {
|
||||
old_config->next = stale_configs_head;
|
||||
stale_configs_head = old_config;
|
||||
}
|
||||
|
||||
log_info("Hot-reload complete: %d routes loaded", new_config->route_count);
|
||||
return 1;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,813 +0,0 @@
|
||||
#include "monitor.h"
|
||||
#include "logging.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <math.h>
|
||||
|
||||
system_monitor_t monitor;
|
||||
|
||||
void history_deque_init(history_deque_t *dq, int capacity) {
|
||||
dq->points = calloc(capacity, sizeof(history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void history_deque_push(history_deque_t *dq, double time, double value) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (history_point_t){ .time = time, .value = value };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void network_history_deque_init(network_history_deque_t *dq, int capacity) {
|
||||
dq->points = calloc(capacity, sizeof(network_history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void network_history_deque_push(network_history_deque_t *dq, double time, double rx, double tx) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (network_history_point_t){ .time = time, .rx_kbps = rx, .tx_kbps = tx };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void disk_history_deque_init(disk_history_deque_t *dq, int capacity) {
|
||||
dq->points = calloc(capacity, sizeof(disk_history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void disk_history_deque_push(disk_history_deque_t *dq, double time, double read_mbps, double write_mbps) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (disk_history_point_t){ .time = time, .read_mbps = read_mbps, .write_mbps = write_mbps };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void request_time_deque_init(request_time_deque_t *dq, int capacity) {
|
||||
dq->times = calloc(capacity, sizeof(double));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void request_time_deque_push(request_time_deque_t *dq, double time_ms) {
|
||||
if (!dq || !dq->times) return;
|
||||
dq->times[dq->head] = time_ms;
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
#define DATA_RETENTION_SECONDS (24 * 60 * 60)
|
||||
|
||||
static void init_db(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
char *err_msg = 0;
|
||||
|
||||
sqlite3_exec(monitor.db, "PRAGMA journal_mode=WAL;", 0, 0, NULL);
|
||||
sqlite3_exec(monitor.db, "PRAGMA synchronous=NORMAL;", 0, 0, NULL);
|
||||
|
||||
const char *sql_create_stats =
|
||||
"CREATE TABLE IF NOT EXISTS vhost_stats ("
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" vhost TEXT NOT NULL,"
|
||||
" timestamp REAL NOT NULL,"
|
||||
" http_requests INTEGER DEFAULT 0,"
|
||||
" websocket_requests INTEGER DEFAULT 0,"
|
||||
" total_requests INTEGER DEFAULT 0,"
|
||||
" bytes_sent INTEGER DEFAULT 0,"
|
||||
" bytes_recv INTEGER DEFAULT 0,"
|
||||
" avg_request_time_ms REAL DEFAULT 0,"
|
||||
" UNIQUE(vhost, timestamp)"
|
||||
");";
|
||||
|
||||
const char *sql_create_totals =
|
||||
"CREATE TABLE IF NOT EXISTS vhost_totals ("
|
||||
" vhost TEXT PRIMARY KEY,"
|
||||
" http_requests INTEGER DEFAULT 0,"
|
||||
" websocket_requests INTEGER DEFAULT 0,"
|
||||
" total_requests INTEGER DEFAULT 0,"
|
||||
" bytes_sent INTEGER DEFAULT 0,"
|
||||
" bytes_recv INTEGER DEFAULT 0"
|
||||
");";
|
||||
|
||||
const char *sql_idx_vhost_ts = "CREATE INDEX IF NOT EXISTS idx_vhost_timestamp ON vhost_stats(vhost, timestamp);";
|
||||
const char *sql_idx_ts = "CREATE INDEX IF NOT EXISTS idx_timestamp ON vhost_stats(timestamp);";
|
||||
|
||||
if (sqlite3_exec(monitor.db, sql_create_stats, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
if (sqlite3_exec(monitor.db, sql_create_totals, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
if (sqlite3_exec(monitor.db, sql_idx_vhost_ts, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
if (sqlite3_exec(monitor.db, sql_idx_ts, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void load_stats_from_db(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
sqlite3_stmt *res;
|
||||
const char *sql =
|
||||
"SELECT vhost, http_requests, websocket_requests, total_requests, "
|
||||
"bytes_sent, bytes_recv FROM vhost_totals";
|
||||
|
||||
if (sqlite3_prepare_v2(monitor.db, sql, -1, &res, 0) != SQLITE_OK) {
|
||||
fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(monitor.db));
|
||||
return;
|
||||
}
|
||||
|
||||
int vhost_count = 0;
|
||||
while (sqlite3_step(res) == SQLITE_ROW) {
|
||||
vhost_stats_t *stats = monitor_get_or_create_vhost_stats((const char*)sqlite3_column_text(res, 0));
|
||||
if (stats) {
|
||||
stats->http_requests = sqlite3_column_int64(res, 1);
|
||||
stats->websocket_requests = sqlite3_column_int64(res, 2);
|
||||
stats->total_requests = sqlite3_column_int64(res, 3);
|
||||
stats->bytes_sent = sqlite3_column_int64(res, 4);
|
||||
stats->bytes_recv = sqlite3_column_int64(res, 5);
|
||||
vhost_count++;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(res);
|
||||
log_info("Loaded statistics for %d vhosts from database", vhost_count);
|
||||
}
|
||||
|
||||
void monitor_init(const char *db_file) {
|
||||
memset(&monitor, 0, sizeof(system_monitor_t));
|
||||
monitor.start_time = time(NULL);
|
||||
monitor.uptime_start = time(NULL);
|
||||
monitor.health_score = 100.0;
|
||||
|
||||
history_deque_init(&monitor.cpu_history, HISTORY_SECONDS);
|
||||
history_deque_init(&monitor.memory_history, HISTORY_SECONDS);
|
||||
network_history_deque_init(&monitor.network_history, HISTORY_SECONDS);
|
||||
disk_history_deque_init(&monitor.disk_history, HISTORY_SECONDS);
|
||||
history_deque_init(&monitor.throughput_history, HISTORY_SECONDS);
|
||||
history_deque_init(&monitor.load1_history, HISTORY_SECONDS);
|
||||
history_deque_init(&monitor.load5_history, HISTORY_SECONDS);
|
||||
history_deque_init(&monitor.load15_history, HISTORY_SECONDS);
|
||||
|
||||
histogram_init(&monitor.global_latency);
|
||||
histogram_init(&monitor.connection_lifetime);
|
||||
rate_tracker_init(&monitor.global_rps);
|
||||
|
||||
if (sqlite3_open(db_file, &monitor.db) != SQLITE_OK) {
|
||||
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(monitor.db));
|
||||
if (monitor.db) {
|
||||
sqlite3_close(monitor.db);
|
||||
monitor.db = NULL;
|
||||
}
|
||||
} else {
|
||||
init_db();
|
||||
load_stats_from_db();
|
||||
}
|
||||
monitor_update();
|
||||
}
|
||||
|
||||
void monitor_cleanup(void) {
|
||||
if (monitor.db) {
|
||||
sqlite3_close(monitor.db);
|
||||
monitor.db = NULL;
|
||||
}
|
||||
|
||||
vhost_stats_t *current = monitor.vhost_stats_head;
|
||||
while (current) {
|
||||
vhost_stats_t *next = current->next;
|
||||
if (current->throughput_history.points) free(current->throughput_history.points);
|
||||
if (current->request_times.times) free(current->request_times.times);
|
||||
free(current);
|
||||
current = next;
|
||||
}
|
||||
monitor.vhost_stats_head = NULL;
|
||||
|
||||
if (monitor.cpu_history.points) { free(monitor.cpu_history.points); monitor.cpu_history.points = NULL; }
|
||||
if (monitor.memory_history.points) { free(monitor.memory_history.points); monitor.memory_history.points = NULL; }
|
||||
if (monitor.network_history.points) { free(monitor.network_history.points); monitor.network_history.points = NULL; }
|
||||
if (monitor.disk_history.points) { free(monitor.disk_history.points); monitor.disk_history.points = NULL; }
|
||||
if (monitor.throughput_history.points) { free(monitor.throughput_history.points); monitor.throughput_history.points = NULL; }
|
||||
if (monitor.load1_history.points) { free(monitor.load1_history.points); monitor.load1_history.points = NULL; }
|
||||
if (monitor.load5_history.points) { free(monitor.load5_history.points); monitor.load5_history.points = NULL; }
|
||||
if (monitor.load15_history.points) { free(monitor.load15_history.points); monitor.load15_history.points = NULL; }
|
||||
}
|
||||
|
||||
static void cleanup_old_stats(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
double cutoff = (double)time(NULL) - DATA_RETENTION_SECONDS;
|
||||
sqlite3_stmt *stmt;
|
||||
const char *sql = "DELETE FROM vhost_stats WHERE timestamp < ?;";
|
||||
|
||||
if (sqlite3_prepare_v2(monitor.db, sql, -1, &stmt, NULL) != SQLITE_OK) return;
|
||||
sqlite3_bind_double(stmt, 1, cutoff);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
static void save_stats_to_db(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
sqlite3_exec(monitor.db, "BEGIN TRANSACTION;", 0, 0, NULL);
|
||||
|
||||
sqlite3_stmt *stmt_totals;
|
||||
const char *sql_totals =
|
||||
"INSERT OR REPLACE INTO vhost_totals "
|
||||
"(vhost, http_requests, websocket_requests, total_requests, bytes_sent, bytes_recv) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?);";
|
||||
|
||||
sqlite3_stmt *stmt_stats;
|
||||
const char *sql_stats =
|
||||
"INSERT OR REPLACE INTO vhost_stats "
|
||||
"(vhost, timestamp, http_requests, websocket_requests, total_requests, "
|
||||
"bytes_sent, bytes_recv, avg_request_time_ms) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?);";
|
||||
|
||||
if (sqlite3_prepare_v2(monitor.db, sql_totals, -1, &stmt_totals, NULL) != SQLITE_OK) {
|
||||
sqlite3_exec(monitor.db, "ROLLBACK;", 0, 0, NULL);
|
||||
return;
|
||||
}
|
||||
if (sqlite3_prepare_v2(monitor.db, sql_stats, -1, &stmt_stats, NULL) != SQLITE_OK) {
|
||||
sqlite3_finalize(stmt_totals);
|
||||
sqlite3_exec(monitor.db, "ROLLBACK;", 0, 0, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
double current_time = (double)time(NULL);
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) {
|
||||
if (s->request_times.count > 0) {
|
||||
double total_time = 0;
|
||||
for(int i = 0; i < s->request_times.count; i++) {
|
||||
total_time += s->request_times.times[i];
|
||||
}
|
||||
s->avg_request_time_ms = total_time / s->request_times.count;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt_totals, 1, s->vhost_name, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(stmt_totals, 2, s->http_requests);
|
||||
sqlite3_bind_int64(stmt_totals, 3, s->websocket_requests);
|
||||
sqlite3_bind_int64(stmt_totals, 4, s->total_requests);
|
||||
sqlite3_bind_int64(stmt_totals, 5, s->bytes_sent);
|
||||
sqlite3_bind_int64(stmt_totals, 6, s->bytes_recv);
|
||||
sqlite3_step(stmt_totals);
|
||||
sqlite3_reset(stmt_totals);
|
||||
|
||||
sqlite3_bind_text(stmt_stats, 1, s->vhost_name, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_double(stmt_stats, 2, current_time);
|
||||
sqlite3_bind_int64(stmt_stats, 3, s->http_requests);
|
||||
sqlite3_bind_int64(stmt_stats, 4, s->websocket_requests);
|
||||
sqlite3_bind_int64(stmt_stats, 5, s->total_requests);
|
||||
sqlite3_bind_int64(stmt_stats, 6, s->bytes_sent);
|
||||
sqlite3_bind_int64(stmt_stats, 7, s->bytes_recv);
|
||||
sqlite3_bind_double(stmt_stats, 8, s->avg_request_time_ms);
|
||||
sqlite3_step(stmt_stats);
|
||||
sqlite3_reset(stmt_stats);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt_totals);
|
||||
sqlite3_finalize(stmt_stats);
|
||||
sqlite3_exec(monitor.db, "COMMIT;", 0, 0, NULL);
|
||||
|
||||
static time_t last_cleanup = 0;
|
||||
if (current_time - last_cleanup >= 3600) {
|
||||
cleanup_old_stats();
|
||||
last_cleanup = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
static double get_cpu_usage(void) {
|
||||
static long long prev_user = 0, prev_nice = 0, prev_system = 0, prev_idle = 0;
|
||||
long long user, nice, system, idle, iowait, irq, softirq;
|
||||
|
||||
FILE *f = fopen("/proc/stat", "r");
|
||||
if (!f) return 0.0;
|
||||
|
||||
if (fscanf(f, "cpu %lld %lld %lld %lld %lld %lld %lld",
|
||||
&user, &nice, &system, &idle, &iowait, &irq, &softirq) != 7) {
|
||||
fclose(f);
|
||||
return 0.0;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
long long prev_total = prev_user + prev_nice + prev_system + prev_idle;
|
||||
long long total = user + nice + system + idle;
|
||||
long long totald = total - prev_total;
|
||||
long long idled = idle - prev_idle;
|
||||
|
||||
prev_user = user; prev_nice = nice; prev_system = system; prev_idle = idle;
|
||||
return totald == 0 ? 0.0 : (double)(totald - idled) * 100.0 / totald;
|
||||
}
|
||||
|
||||
static void get_memory_usage(double *used_gb) {
|
||||
struct sysinfo info;
|
||||
if (sysinfo(&info) != 0) {
|
||||
*used_gb = 0;
|
||||
return;
|
||||
}
|
||||
*used_gb = (double)(info.totalram - info.freeram - info.bufferram) * info.mem_unit / (1024.0 * 1024.0 * 1024.0);
|
||||
}
|
||||
|
||||
static void get_network_stats(long long *bytes_sent, long long *bytes_recv) {
|
||||
FILE *f = fopen("/proc/net/dev", "r");
|
||||
if (!f) {
|
||||
*bytes_sent = 0;
|
||||
*bytes_recv = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
if (!fgets(line, sizeof(line), f) || !fgets(line, sizeof(line), f)) {
|
||||
fclose(f);
|
||||
*bytes_sent = 0;
|
||||
*bytes_recv = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
long long total_recv = 0, total_sent = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char iface[32];
|
||||
long long r, t;
|
||||
if (sscanf(line, "%31[^:]: %lld %*d %*d %*d %*d %*d %*d %*d %lld", iface, &r, &t) == 3) {
|
||||
char *trimmed = iface;
|
||||
while (*trimmed == ' ') trimmed++;
|
||||
if (strcmp(trimmed, "lo") != 0) {
|
||||
total_recv += r;
|
||||
total_sent += t;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
*bytes_sent = total_sent;
|
||||
*bytes_recv = total_recv;
|
||||
}
|
||||
|
||||
static void get_disk_stats(long long *sectors_read, long long *sectors_written) {
|
||||
FILE *f = fopen("/proc/diskstats", "r");
|
||||
if (!f) {
|
||||
*sectors_read = 0;
|
||||
*sectors_written = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char line[2048];
|
||||
long long total_read = 0, total_written = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char device[64];
|
||||
long long sectors_r = 0, sectors_w = 0;
|
||||
int nfields = 0;
|
||||
|
||||
char major[16], minor[16], dev[64];
|
||||
char rc[32], rm[32], sr[32], rtm[32], rtm2[32], wc[32], wm[32], sw[32];
|
||||
nfields = sscanf(line, "%15s %15s %63s %31s %31s %31s %31s %31s %31s %31s %31s %31s",
|
||||
major, minor, dev, rc, rm, sr, rtm, rtm2, wc, wm, sw, sw);
|
||||
if (nfields >= 11) {
|
||||
strncpy(device, dev, sizeof(device)-1);
|
||||
device[sizeof(device)-1] = '\0';
|
||||
char *endptr;
|
||||
sectors_r = strtoll(sr, &endptr, 10);
|
||||
if (endptr == sr) sectors_r = 0;
|
||||
sectors_w = strtoll(sw, &endptr, 10);
|
||||
if (endptr == sw) sectors_w = 0;
|
||||
|
||||
if (strncmp(device, "loop", 4) != 0 && strncmp(device, "ram", 3) != 0) {
|
||||
int len = strlen(device);
|
||||
if ((strncmp(device, "sd", 2) == 0 && len == 3) ||
|
||||
(strncmp(device, "nvme", 4) == 0 && strstr(device, "n1p") == NULL) ||
|
||||
(strncmp(device, "vd", 2) == 0 && len == 3) ||
|
||||
(strncmp(device, "hd", 2) == 0 && len == 3)) {
|
||||
total_read += sectors_r;
|
||||
total_written += sectors_w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
*sectors_read = total_read;
|
||||
*sectors_written = total_written;
|
||||
}
|
||||
|
||||
static void get_load_averages(double *load1, double *load5, double *load15) {
|
||||
FILE *f = fopen("/proc/loadavg", "r");
|
||||
if (!f) {
|
||||
*load1 = *load5 = *load15 = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fscanf(f, "%lf %lf %lf", load1, load5, load15) != 3) {
|
||||
*load1 = *load5 = *load15 = 0.0;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void monitor_update(void) {
|
||||
double current_time = time(NULL);
|
||||
|
||||
history_deque_push(&monitor.cpu_history, current_time, get_cpu_usage());
|
||||
|
||||
double mem_used_gb;
|
||||
get_memory_usage(&mem_used_gb);
|
||||
history_deque_push(&monitor.memory_history, current_time, mem_used_gb);
|
||||
|
||||
long long net_sent, net_recv;
|
||||
get_network_stats(&net_sent, &net_recv);
|
||||
double time_delta = current_time - monitor.last_net_update_time;
|
||||
if (time_delta > 0 && monitor.last_net_update_time > 0) {
|
||||
double rx = (net_recv - monitor.last_net_recv) / time_delta / 1024.0;
|
||||
double tx = (net_sent - monitor.last_net_sent) / time_delta / 1024.0;
|
||||
network_history_deque_push(&monitor.network_history, current_time, fmax(0, rx), fmax(0, tx));
|
||||
history_deque_push(&monitor.throughput_history, current_time, fmax(0, rx + tx));
|
||||
}
|
||||
monitor.last_net_sent = net_sent;
|
||||
monitor.last_net_recv = net_recv;
|
||||
monitor.last_net_update_time = current_time;
|
||||
|
||||
long long disk_read, disk_write;
|
||||
get_disk_stats(&disk_read, &disk_write);
|
||||
double disk_time_delta = current_time - monitor.last_disk_update_time;
|
||||
if (disk_time_delta > 0 && monitor.last_disk_update_time > 0) {
|
||||
double read_mbps = (disk_read - monitor.last_disk_read) * 512.0 / disk_time_delta / (1024.0 * 1024.0);
|
||||
double write_mbps = (disk_write - monitor.last_disk_write) * 512.0 / disk_time_delta / (1024.0 * 1024.0);
|
||||
disk_history_deque_push(&monitor.disk_history, current_time, fmax(0, read_mbps), fmax(0, write_mbps));
|
||||
}
|
||||
monitor.last_disk_read = disk_read;
|
||||
monitor.last_disk_write = disk_write;
|
||||
monitor.last_disk_update_time = current_time;
|
||||
|
||||
double load1, load5, load15;
|
||||
get_load_averages(&load1, &load5, &load15);
|
||||
history_deque_push(&monitor.load1_history, current_time, load1);
|
||||
history_deque_push(&monitor.load5_history, current_time, load5);
|
||||
history_deque_push(&monitor.load15_history, current_time, load15);
|
||||
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) {
|
||||
double vhost_delta = current_time - s->last_update;
|
||||
if (vhost_delta >= 1.0) {
|
||||
double kbps = 0;
|
||||
if (s->last_update > 0) {
|
||||
long long bytes_diff = (s->bytes_sent - s->last_bytes_sent) + (s->bytes_recv - s->last_bytes_recv);
|
||||
kbps = bytes_diff / vhost_delta / 1024.0;
|
||||
}
|
||||
history_deque_push(&s->throughput_history, current_time, fmax(0, kbps));
|
||||
s->last_bytes_sent = s->bytes_sent;
|
||||
s->last_bytes_recv = s->bytes_recv;
|
||||
s->last_update = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
static time_t last_db_save = 0;
|
||||
if (current_time - last_db_save >= 10) {
|
||||
save_stats_to_db();
|
||||
last_db_save = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
vhost_stats_t* monitor_get_or_create_vhost_stats(const char *vhost_name) {
|
||||
if (!vhost_name || strlen(vhost_name) == 0) return NULL;
|
||||
|
||||
for (vhost_stats_t *curr = monitor.vhost_stats_head; curr; curr = curr->next) {
|
||||
if (strcmp(curr->vhost_name, vhost_name) == 0) {
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
vhost_stats_t *new_stats = calloc(1, sizeof(vhost_stats_t));
|
||||
if (!new_stats) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strncpy(new_stats->vhost_name, vhost_name, sizeof(new_stats->vhost_name) - 1);
|
||||
new_stats->last_update = time(NULL);
|
||||
history_deque_init(&new_stats->throughput_history, 60);
|
||||
request_time_deque_init(&new_stats->request_times, 100);
|
||||
|
||||
histogram_init(&new_stats->latency_histogram);
|
||||
histogram_init(&new_stats->request_size_histogram);
|
||||
histogram_init(&new_stats->response_size_histogram);
|
||||
histogram_init(&new_stats->ttfb_histogram);
|
||||
histogram_init(&new_stats->upstream_connect_latency);
|
||||
rate_tracker_init(&new_stats->requests_per_second);
|
||||
|
||||
new_stats->next = monitor.vhost_stats_head;
|
||||
monitor.vhost_stats_head = new_stats;
|
||||
|
||||
return new_stats;
|
||||
}
|
||||
|
||||
void monitor_record_request_start(vhost_stats_t *stats, int is_websocket) {
|
||||
if (!stats) return;
|
||||
if (is_websocket) {
|
||||
stats->websocket_requests++;
|
||||
} else {
|
||||
stats->http_requests++;
|
||||
}
|
||||
stats->total_requests++;
|
||||
rate_tracker_increment(&stats->requests_per_second);
|
||||
rate_tracker_increment(&monitor.global_rps);
|
||||
}
|
||||
|
||||
void monitor_record_request_end(vhost_stats_t *stats, double start_time) {
|
||||
if (!stats || start_time <= 0) return;
|
||||
struct timespec end_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||
double duration_ms = ((end_time.tv_sec + end_time.tv_nsec / 1e9) - start_time) * 1000.0;
|
||||
if (duration_ms >= 0 && duration_ms < 60000) {
|
||||
request_time_deque_push(&stats->request_times, duration_ms);
|
||||
histogram_add(&stats->latency_histogram, duration_ms);
|
||||
histogram_add(&monitor.global_latency, duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void monitor_record_bytes(vhost_stats_t *stats, long long sent, long long recv) {
|
||||
if (!stats) return;
|
||||
stats->bytes_sent += sent;
|
||||
stats->bytes_recv += recv;
|
||||
}
|
||||
|
||||
const double LATENCY_BUCKET_BOUNDS[HISTOGRAM_BUCKETS] = {
|
||||
1.0, 2.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0,
|
||||
500.0, 1000.0, 2500.0, 5000.0, 10000.0, 30000.0, 60000.0, 1e9
|
||||
};
|
||||
|
||||
const char* LATENCY_BUCKET_LABELS[HISTOGRAM_BUCKETS] = {
|
||||
"0-1ms", "1-2ms", "2-5ms", "5-10ms", "10-25ms", "25-50ms", "50-100ms", "100-250ms",
|
||||
"250-500ms", "500ms-1s", "1-2.5s", "2.5-5s", "5-10s", "10-30s", "30-60s", "60s+"
|
||||
};
|
||||
|
||||
const double SIZE_BUCKET_BOUNDS[HISTOGRAM_BUCKETS] = {
|
||||
128.0, 512.0, 1024.0, 4096.0, 16384.0, 65536.0, 262144.0, 1048576.0,
|
||||
4194304.0, 16777216.0, 67108864.0, 268435456.0, 1073741824.0, 4294967296.0, 1e15, 1e18
|
||||
};
|
||||
|
||||
void histogram_init(histogram_t *h) {
|
||||
if (!h) return;
|
||||
memset(h, 0, sizeof(histogram_t));
|
||||
h->min_value = 1e18;
|
||||
h->max_value = -1e18;
|
||||
}
|
||||
|
||||
void histogram_add(histogram_t *h, double value) {
|
||||
if (!h) return;
|
||||
h->total_count++;
|
||||
h->sum += value;
|
||||
if (value < h->min_value) h->min_value = value;
|
||||
if (value > h->max_value) h->max_value = value;
|
||||
|
||||
for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
|
||||
if (value <= LATENCY_BUCKET_BOUNDS[i]) {
|
||||
h->buckets[i]++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
h->overflow++;
|
||||
}
|
||||
|
||||
static void histogram_add_with_bounds(histogram_t *h, double value, const double *bounds) {
|
||||
if (!h) return;
|
||||
h->total_count++;
|
||||
h->sum += value;
|
||||
if (value < h->min_value) h->min_value = value;
|
||||
if (value > h->max_value) h->max_value = value;
|
||||
|
||||
for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
|
||||
if (value <= bounds[i]) {
|
||||
h->buckets[i]++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
h->overflow++;
|
||||
}
|
||||
|
||||
double histogram_percentile(histogram_t *h, double p) {
|
||||
if (!h || h->total_count == 0) return 0.0;
|
||||
uint64_t target = (uint64_t)(h->total_count * p);
|
||||
uint64_t cumulative = 0;
|
||||
|
||||
for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
|
||||
cumulative += h->buckets[i];
|
||||
if (cumulative >= target) {
|
||||
return LATENCY_BUCKET_BOUNDS[i];
|
||||
}
|
||||
}
|
||||
return LATENCY_BUCKET_BOUNDS[HISTOGRAM_BUCKETS - 1];
|
||||
}
|
||||
|
||||
double histogram_mean(histogram_t *h) {
|
||||
if (!h || h->total_count == 0) return 0.0;
|
||||
return h->sum / h->total_count;
|
||||
}
|
||||
|
||||
void rate_tracker_init(rate_tracker_t *rt) {
|
||||
if (!rt) return;
|
||||
memset(rt, 0, sizeof(rate_tracker_t));
|
||||
rt->slot_start = time(NULL);
|
||||
}
|
||||
|
||||
void rate_tracker_increment(rate_tracker_t *rt) {
|
||||
if (!rt) return;
|
||||
time_t now = time(NULL);
|
||||
int slot = now % RATE_TRACKER_SLOTS;
|
||||
|
||||
if (now != rt->slot_start) {
|
||||
int slots_to_clear = (int)(now - rt->slot_start);
|
||||
if (slots_to_clear >= RATE_TRACKER_SLOTS) {
|
||||
memset(rt->counts, 0, sizeof(rt->counts));
|
||||
} else {
|
||||
for (int i = 1; i <= slots_to_clear; i++) {
|
||||
int clear_slot = (rt->current_slot + i) % RATE_TRACKER_SLOTS;
|
||||
rt->counts[clear_slot] = 0;
|
||||
}
|
||||
}
|
||||
rt->current_slot = slot;
|
||||
rt->slot_start = now;
|
||||
}
|
||||
|
||||
rt->counts[slot]++;
|
||||
}
|
||||
|
||||
uint32_t rate_tracker_get_rps(rate_tracker_t *rt) {
|
||||
if (!rt) return 0;
|
||||
time_t now = time(NULL);
|
||||
int prev_slot = (now - 1) % RATE_TRACKER_SLOTS;
|
||||
if ((now - rt->slot_start) > RATE_TRACKER_SLOTS) return 0;
|
||||
return rt->counts[prev_slot];
|
||||
}
|
||||
|
||||
uint32_t rate_tracker_get_total_last_minute(rate_tracker_t *rt) {
|
||||
if (!rt) return 0;
|
||||
time_t now = time(NULL);
|
||||
if ((now - rt->slot_start) > RATE_TRACKER_SLOTS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t total = 0;
|
||||
for (int i = 0; i < RATE_TRACKER_SLOTS; i++) {
|
||||
total += rt->counts[i];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
http_method_t http_method_from_string(const char *method) {
|
||||
if (!method) return HTTP_METHOD_OTHER;
|
||||
if (strcmp(method, "GET") == 0) return HTTP_METHOD_GET;
|
||||
if (strcmp(method, "POST") == 0) return HTTP_METHOD_POST;
|
||||
if (strcmp(method, "PUT") == 0) return HTTP_METHOD_PUT;
|
||||
if (strcmp(method, "DELETE") == 0) return HTTP_METHOD_DELETE;
|
||||
if (strcmp(method, "PATCH") == 0) return HTTP_METHOD_PATCH;
|
||||
if (strcmp(method, "HEAD") == 0) return HTTP_METHOD_HEAD;
|
||||
if (strcmp(method, "OPTIONS") == 0) return HTTP_METHOD_OPTIONS;
|
||||
return HTTP_METHOD_OTHER;
|
||||
}
|
||||
|
||||
void monitor_record_method(vhost_stats_t *stats, http_method_t method) {
|
||||
if (!stats || method >= HTTP_METHOD_COUNT) return;
|
||||
stats->method_counts.counts[method]++;
|
||||
}
|
||||
|
||||
void monitor_record_status(vhost_stats_t *stats, int status_code) {
|
||||
if (!stats) return;
|
||||
if (status_code >= 100 && status_code < 200) {
|
||||
stats->status_counts.status_1xx++;
|
||||
} else if (status_code >= 200 && status_code < 300) {
|
||||
stats->status_counts.status_2xx++;
|
||||
} else if (status_code >= 300 && status_code < 400) {
|
||||
stats->status_counts.status_3xx++;
|
||||
} else if (status_code >= 400 && status_code < 500) {
|
||||
stats->status_counts.status_4xx++;
|
||||
} else if (status_code >= 500 && status_code < 600) {
|
||||
stats->status_counts.status_5xx++;
|
||||
} else {
|
||||
stats->status_counts.status_unknown++;
|
||||
}
|
||||
}
|
||||
|
||||
void monitor_record_request_size(vhost_stats_t *stats, long size) {
|
||||
if (!stats || size < 0) return;
|
||||
histogram_add_with_bounds(&stats->request_size_histogram, (double)size, SIZE_BUCKET_BOUNDS);
|
||||
}
|
||||
|
||||
void monitor_record_response_size(vhost_stats_t *stats, long size) {
|
||||
if (!stats || size < 0) return;
|
||||
histogram_add_with_bounds(&stats->response_size_histogram, (double)size, SIZE_BUCKET_BOUNDS);
|
||||
stats->response_bytes_total += size;
|
||||
}
|
||||
|
||||
void monitor_record_ttfb(vhost_stats_t *stats, double ttfb_ms) {
|
||||
if (!stats || ttfb_ms < 0) return;
|
||||
histogram_add(&stats->ttfb_histogram, ttfb_ms);
|
||||
}
|
||||
|
||||
void monitor_record_upstream_connect(vhost_stats_t *stats, int success, double latency_ms) {
|
||||
if (!stats) return;
|
||||
if (success) {
|
||||
stats->upstream_connect_success++;
|
||||
if (latency_ms >= 0) {
|
||||
histogram_add(&stats->upstream_connect_latency, latency_ms);
|
||||
}
|
||||
} else {
|
||||
stats->upstream_connect_failures++;
|
||||
}
|
||||
}
|
||||
|
||||
void monitor_record_splice_transfer(vhost_stats_t *stats, long long bytes) {
|
||||
if (!stats) return;
|
||||
stats->splice_transfers++;
|
||||
stats->bytes_via_splice += bytes;
|
||||
}
|
||||
|
||||
void monitor_record_buffer_transfer(vhost_stats_t *stats, long long bytes) {
|
||||
if (!stats) return;
|
||||
stats->buffered_transfers++;
|
||||
stats->bytes_via_buffer += bytes;
|
||||
}
|
||||
|
||||
void monitor_record_connection_opened(vhost_stats_t *stats) {
|
||||
if (!stats) return;
|
||||
stats->connections_opened++;
|
||||
monitor.total_connections_accepted++;
|
||||
}
|
||||
|
||||
void monitor_record_connection_closed(vhost_stats_t *stats) {
|
||||
if (!stats) return;
|
||||
stats->connections_closed++;
|
||||
}
|
||||
|
||||
void monitor_record_keepalive_reuse(vhost_stats_t *stats) {
|
||||
if (!stats) return;
|
||||
stats->keep_alive_reused++;
|
||||
}
|
||||
|
||||
void monitor_record_error(vhost_stats_t *stats, int error_type) {
|
||||
if (!stats) return;
|
||||
switch (error_type) {
|
||||
case ERROR_TYPE_DNS:
|
||||
stats->dns_failures++;
|
||||
break;
|
||||
case ERROR_TYPE_SSL:
|
||||
stats->ssl_failures++;
|
||||
break;
|
||||
case ERROR_TYPE_TIMEOUT:
|
||||
stats->timeout_errors++;
|
||||
break;
|
||||
case ERROR_TYPE_CONNECTION:
|
||||
stats->connection_errors++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void monitor_compute_health_score(void) {
|
||||
uint64_t total_requests = 0;
|
||||
uint64_t total_errors = 0;
|
||||
uint64_t total_upstream_failures = 0;
|
||||
uint64_t total_upstream_attempts = 0;
|
||||
uint64_t total_timeouts = 0;
|
||||
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) {
|
||||
total_requests += s->total_requests;
|
||||
total_errors += s->status_counts.status_5xx;
|
||||
total_upstream_failures += s->upstream_connect_failures;
|
||||
total_upstream_attempts += s->upstream_connect_success + s->upstream_connect_failures;
|
||||
total_timeouts += s->timeout_errors;
|
||||
}
|
||||
|
||||
double error_rate = total_requests > 0 ? (double)total_errors / total_requests : 0;
|
||||
double upstream_fail_rate = total_upstream_attempts > 0 ? (double)total_upstream_failures / total_upstream_attempts : 0;
|
||||
double timeout_rate = total_requests > 0 ? (double)total_timeouts / total_requests : 0;
|
||||
|
||||
monitor.health_score = 100.0 * (1.0 - (error_rate * 0.5 + upstream_fail_rate * 0.3 + timeout_rate * 0.2));
|
||||
if (monitor.health_score < 0) monitor.health_score = 0;
|
||||
if (monitor.health_score > 100) monitor.health_score = 100;
|
||||
|
||||
monitor.error_rate_1m = error_rate;
|
||||
}
|
||||
|
||||
void monitor_update_connection_states(void) {
|
||||
memset(monitor.connections_by_state, 0, sizeof(monitor.connections_by_state));
|
||||
}
|
||||
|
||||
double monitor_get_current_rps(void) {
|
||||
uint32_t total_rps = 0;
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) {
|
||||
total_rps += rate_tracker_get_rps(&s->requests_per_second);
|
||||
}
|
||||
|
||||
double rps = (double)total_rps;
|
||||
if (rps > monitor.peak_rps) {
|
||||
monitor.peak_rps = rps;
|
||||
monitor.peak_rps_time = time(NULL);
|
||||
}
|
||||
return rps;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "base64.h"
|
||||
#include <string.h>
|
||||
|
||||
static int base64_decode_char(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int base64_decode(const char *input, char *output, size_t output_size) {
|
||||
if (!input || !output || output_size == 0) return -1;
|
||||
|
||||
size_t input_len = strlen(input);
|
||||
size_t output_idx = 0;
|
||||
|
||||
for (size_t i = 0; i < input_len && output_idx < output_size - 1; i += 4) {
|
||||
int v[4] = {0, 0, 0, 0};
|
||||
int pad = 0;
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (i + (size_t)j >= input_len || input[i + j] == '=') {
|
||||
pad++;
|
||||
v[j] = 0;
|
||||
} else {
|
||||
v[j] = base64_decode_char(input[i + j]);
|
||||
if (v[j] < 0) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (output_idx < output_size - 1) output[output_idx++] = (char)((v[0] << 2) | (v[1] >> 4));
|
||||
if (pad < 2 && output_idx < output_size - 1) output[output_idx++] = (char)((v[1] << 4) | (v[2] >> 2));
|
||||
if (pad < 1 && output_idx < output_size - 1) output[output_idx++] = (char)((v[2] << 6) | v[3]);
|
||||
}
|
||||
|
||||
output[output_idx] = '\0';
|
||||
return (int)output_idx;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_BASE64_H
|
||||
#define RPROXY_BASE64_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int base64_decode(const char *input, char *output, size_t output_size);
|
||||
|
||||
#endif
|
||||
Executable → Regular
Executable → Regular
@@ -1,141 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "client_handler.h"
|
||||
#include "buffer.h"
|
||||
#include "http.h"
|
||||
#include "http_response.h"
|
||||
#include "config.h"
|
||||
#include "rate_limit.h"
|
||||
#include "auth.h"
|
||||
#include "monitor.h"
|
||||
#include "dashboard.h"
|
||||
#include "upstream.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
extern connection_t connections[MAX_FDS];
|
||||
extern time_t cached_time;
|
||||
|
||||
static int client_has_complete_request(connection_t *conn) {
|
||||
size_t available = conn->read_buf.tail - conn->read_buf.head;
|
||||
if (available < 4) return 0;
|
||||
|
||||
char *headers_end = memmem(conn->read_buf.data + conn->read_buf.head, available, "\r\n\r\n", 4);
|
||||
return headers_end != NULL;
|
||||
}
|
||||
|
||||
int client_check_rate_limit(connection_t *conn) {
|
||||
if (!rate_limit_check(conn->client_ip)) {
|
||||
http_response_send_error(conn, 429, "Too Many Requests",
|
||||
"429 Too Many Requests - Rate limit exceeded");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int client_check_route_auth(connection_t *conn, route_config_t *route, const char *data, size_t len) {
|
||||
if (!route || !route->use_auth) return 1;
|
||||
|
||||
char auth_header[512] = "";
|
||||
http_find_header_value(data, len, "Authorization", auth_header, sizeof(auth_header));
|
||||
|
||||
char error_msg[256] = "";
|
||||
if (!auth_check_route_basic_auth(route, auth_header[0] ? auth_header : NULL, error_msg, sizeof(error_msg))) {
|
||||
http_response_send_auth_required(conn, route->hostname);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int client_handle_internal_route(connection_t *conn, const char *data, size_t len) {
|
||||
if (!http_uri_is_internal_route(conn->request.uri)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
conn->state = CLIENT_STATE_SERVING_INTERNAL;
|
||||
|
||||
if (strcmp(conn->request.uri, "/rproxy/dashboard") == 0) {
|
||||
dashboard_serve(conn, data, len);
|
||||
} else if (strcmp(conn->request.uri, "/rproxy/api/stats") == 0) {
|
||||
dashboard_serve_stats_api(conn, data, len);
|
||||
} else {
|
||||
http_response_send_error(conn, 404, "Not Found", "404 Not Found");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void client_setup_vhost_stats(connection_t *conn) {
|
||||
if (!conn->vhost_stats && conn->request.host[0] != '\0') {
|
||||
conn->vhost_stats = monitor_get_or_create_vhost_stats(conn->request.host);
|
||||
if (conn->vhost_stats) {
|
||||
monitor_record_request_start(conn->vhost_stats, conn->request.is_websocket);
|
||||
monitor_record_method(conn->vhost_stats, http_method_from_string(conn->request.method));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void client_handle_read(connection_t *conn) {
|
||||
if (!conn || conn->state == CLIENT_STATE_CLOSING) return;
|
||||
|
||||
size_t available = buffer_available_write(&conn->read_buf);
|
||||
if (available == 0) {
|
||||
if (buffer_ensure_capacity(&conn->read_buf, conn->read_buf.capacity * 2) < 0) {
|
||||
http_response_send_error(conn, 413, "Request Entity Too Large",
|
||||
"413 Request Entity Too Large");
|
||||
return;
|
||||
}
|
||||
available = buffer_available_write(&conn->read_buf);
|
||||
}
|
||||
|
||||
ssize_t n = read(conn->fd, conn->read_buf.data + conn->read_buf.tail, available);
|
||||
if (n > 0) {
|
||||
conn->read_buf.tail += (size_t)n;
|
||||
conn->last_activity = cached_time;
|
||||
} else if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
return;
|
||||
}
|
||||
|
||||
if (conn->state == CLIENT_STATE_READING_HEADERS) {
|
||||
if (!client_has_complete_request(conn)) return;
|
||||
|
||||
size_t data_len = conn->read_buf.tail - conn->read_buf.head;
|
||||
char *data = conn->read_buf.data + conn->read_buf.head;
|
||||
|
||||
char *headers_end = memmem(data, data_len, "\r\n\r\n", 4);
|
||||
size_t request_len = headers_end ? (size_t)(headers_end - data) + 4 : data_len;
|
||||
|
||||
int parse_result = http_parse_request(data, data_len, &conn->request);
|
||||
if (parse_result <= 0) {
|
||||
http_response_send_error(conn, 400, "Bad Request", "400 Bad Request - Invalid HTTP request");
|
||||
return;
|
||||
}
|
||||
|
||||
client_setup_vhost_stats(conn);
|
||||
|
||||
if (!client_check_rate_limit(conn)) return;
|
||||
|
||||
if (client_handle_internal_route(conn, data, data_len)) {
|
||||
conn->read_buf.head += request_len;
|
||||
if (conn->read_buf.head >= conn->read_buf.tail) {
|
||||
conn->read_buf.head = 0;
|
||||
conn->read_buf.tail = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
route_config_t *route = config_find_route(conn->request.host);
|
||||
if (!route) {
|
||||
http_response_send_error(conn, 502, "Bad Gateway", "502 Bad Gateway - No route configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!client_check_route_auth(conn, route, data, data_len)) return;
|
||||
|
||||
conn->state = CLIENT_STATE_FORWARDING;
|
||||
upstream_connect(conn, data, data_len);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_CLIENT_HANDLER_H
|
||||
#define RPROXY_CLIENT_HANDLER_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void client_handle_read(connection_t *conn);
|
||||
int client_check_rate_limit(connection_t *conn);
|
||||
int client_check_route_auth(connection_t *conn, route_config_t *route, const char *data, size_t len);
|
||||
int client_handle_internal_route(connection_t *conn, const char *data, size_t len);
|
||||
|
||||
#endif
|
||||
+360
-77
@@ -1,88 +1,119 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "config.h"
|
||||
#include "config_parser.h"
|
||||
#include "logging.h"
|
||||
#include "../cJSON.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
static time_t config_file_mtime = 0;
|
||||
|
||||
static void compute_password_hash(const char *password, char *output, size_t output_size) {
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
if (!ctx) return;
|
||||
|
||||
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len = 0;
|
||||
|
||||
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
|
||||
EVP_DigestUpdate(ctx, password, strlen(password));
|
||||
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
for (unsigned int i = 0; i < hash_len && (i * 2 + 2) < output_size; i++) {
|
||||
snprintf(output + (i * 2), 3, "%02x", hash[i]);
|
||||
}
|
||||
}
|
||||
|
||||
app_config_t *config = NULL;
|
||||
static app_config_t *stale_configs_head = NULL;
|
||||
|
||||
static app_config_t *config_create_from_json(cJSON *root) {
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
if (!new_config) {
|
||||
log_error("Failed to allocate memory for config");
|
||||
return NULL;
|
||||
}
|
||||
new_config->ref_count = 1;
|
||||
static int is_valid_hostname(const char *hostname) {
|
||||
if (!hostname || strlen(hostname) == 0 || strlen(hostname) > 253) return 0;
|
||||
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
new_config->port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
const char *p = hostname;
|
||||
int label_len = 0;
|
||||
|
||||
if (new_config->port < 1 || new_config->port > 65535) {
|
||||
log_error("Invalid port number: %d", new_config->port);
|
||||
free(new_config);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON *proxy_array = cJSON_GetObjectItem(root, "reverse_proxy");
|
||||
if (!cJSON_IsArray(proxy_array)) {
|
||||
log_error("No reverse_proxy array found");
|
||||
free(new_config);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int total_routes = cJSON_GetArraySize(proxy_array);
|
||||
if (total_routes <= 0) {
|
||||
log_error("Empty reverse_proxy array");
|
||||
free(new_config);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_config->routes = calloc((size_t)total_routes, sizeof(route_config_t));
|
||||
if (!new_config->routes) {
|
||||
log_error("Failed to allocate memory for routes");
|
||||
free(new_config);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int valid_routes = 0;
|
||||
int index = 0;
|
||||
cJSON *route_item;
|
||||
cJSON_ArrayForEach(route_item, proxy_array) {
|
||||
route_config_t *route = &new_config->routes[valid_routes];
|
||||
if (config_parse_route(route_item, route, index)) {
|
||||
log_info("Route: %s -> %s:%d (SSL: %s, Auth: %s, Patches: %d)",
|
||||
route->hostname, route->upstream_host, route->upstream_port,
|
||||
route->use_ssl ? "yes" : "no", route->use_auth ? "yes" : "no",
|
||||
route->patches.rule_count);
|
||||
valid_routes++;
|
||||
while (*p) {
|
||||
char c = *p;
|
||||
if (c == '.') {
|
||||
if (label_len == 0) return 0;
|
||||
label_len = 0;
|
||||
} else if (isalnum((unsigned char)c) || c == '-' || c == '_') {
|
||||
label_len++;
|
||||
if (label_len > 63) return 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
index++;
|
||||
p++;
|
||||
}
|
||||
|
||||
if (valid_routes == 0) {
|
||||
log_error("No valid routes parsed");
|
||||
free(new_config->routes);
|
||||
free(new_config);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_valid_ip(const char *ip) {
|
||||
if (!ip) return 0;
|
||||
int dots = 0;
|
||||
int num = 0;
|
||||
int has_digit = 0;
|
||||
|
||||
while (*ip) {
|
||||
if (*ip == '.') {
|
||||
if (!has_digit || num > 255) return 0;
|
||||
dots++;
|
||||
num = 0;
|
||||
has_digit = 0;
|
||||
} else if (isdigit((unsigned char)*ip)) {
|
||||
num = num * 10 + (*ip - '0');
|
||||
has_digit = 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
ip++;
|
||||
}
|
||||
|
||||
return dots == 3 && has_digit && num <= 255;
|
||||
}
|
||||
|
||||
static int is_valid_host(const char *host) {
|
||||
return is_valid_hostname(host) || is_valid_ip(host);
|
||||
}
|
||||
|
||||
static char* read_file_to_string(const char *filename) {
|
||||
FILE *f = fopen(filename, "rb");
|
||||
if (!f) return NULL;
|
||||
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
long length = ftell(f);
|
||||
if (length < 0 || length > 1024*1024) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_config->route_count = valid_routes;
|
||||
return new_config;
|
||||
if (fseek(f, 0, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
char *buffer = malloc((size_t)length + 1);
|
||||
if (!buffer) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
size_t read_len = fread(buffer, 1, (size_t)length, f);
|
||||
buffer[read_len] = '\0';
|
||||
fclose(f);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int config_load(const char *filename) {
|
||||
log_info("Loading configuration from %s", filename);
|
||||
|
||||
char *json_string = config_read_file(filename);
|
||||
char *json_string = read_file_to_string(filename);
|
||||
if (!json_string) {
|
||||
log_error("Could not read config file");
|
||||
return 0;
|
||||
@@ -91,23 +122,149 @@ int config_load(const char *filename) {
|
||||
cJSON *root = cJSON_Parse(json_string);
|
||||
if (!root) {
|
||||
const char *error_ptr = cJSON_GetErrorPtr();
|
||||
log_error("JSON parse error: %s", error_ptr ? error_ptr : "unknown");
|
||||
fprintf(stderr, "JSON parse error: %s\n", error_ptr ? error_ptr : "unknown");
|
||||
free(json_string);
|
||||
return 0;
|
||||
}
|
||||
free(json_string);
|
||||
|
||||
app_config_t *new_config = config_create_from_json(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
if (!new_config) {
|
||||
log_error("Failed to allocate memory for new config");
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
new_config->ref_count = 1; // Start with one reference for the global 'config' pointer
|
||||
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
new_config->port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
|
||||
if (new_config->port < 1 || new_config->port > 65535) {
|
||||
fprintf(stderr, "Invalid port number: %d\n", new_config->port);
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(filename, &st) == 0) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
cJSON *proxy_array = cJSON_GetObjectItem(root, "reverse_proxy");
|
||||
if (cJSON_IsArray(proxy_array)) {
|
||||
new_config->route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (new_config->route_count <= 0) {
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
new_config->routes = calloc(new_config->route_count, sizeof(route_config_t));
|
||||
if (!new_config->routes) {
|
||||
log_error("Failed to allocate memory for routes");
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
cJSON *route_item;
|
||||
cJSON_ArrayForEach(route_item, proxy_array) {
|
||||
route_config_t *route = &new_config->routes[i];
|
||||
|
||||
cJSON *hostname = cJSON_GetObjectItem(route_item, "hostname");
|
||||
cJSON *upstream_host = cJSON_GetObjectItem(route_item, "upstream_host");
|
||||
cJSON *upstream_port = cJSON_GetObjectItem(route_item, "upstream_port");
|
||||
|
||||
if (!cJSON_IsString(hostname) || !cJSON_IsString(upstream_host) || !cJSON_IsNumber(upstream_port)) {
|
||||
fprintf(stderr, "Invalid route configuration at index %d\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_valid_host(hostname->valuestring)) {
|
||||
fprintf(stderr, "Invalid hostname at index %d: %s\n", i, hostname->valuestring);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_valid_host(upstream_host->valuestring)) {
|
||||
fprintf(stderr, "Invalid upstream_host at index %d: %s\n", i, upstream_host->valuestring);
|
||||
continue;
|
||||
}
|
||||
|
||||
strncpy(route->hostname, hostname->valuestring, sizeof(route->hostname) - 1);
|
||||
route->hostname[sizeof(route->hostname) - 1] = '\0';
|
||||
strncpy(route->upstream_host, upstream_host->valuestring, sizeof(route->upstream_host) - 1);
|
||||
route->upstream_host[sizeof(route->upstream_host) - 1] = '\0';
|
||||
route->upstream_port = upstream_port->valueint;
|
||||
|
||||
if (route->upstream_port < 1 || route->upstream_port > 65535) {
|
||||
fprintf(stderr, "Invalid upstream port for %s: %d\n", route->hostname, route->upstream_port);
|
||||
continue;
|
||||
}
|
||||
|
||||
route->use_ssl = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "use_ssl"));
|
||||
route->rewrite_host = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "rewrite_host"));
|
||||
|
||||
route->use_auth = 0;
|
||||
route->username[0] = '\0';
|
||||
route->password_hash[0] = '\0';
|
||||
|
||||
cJSON *use_auth = cJSON_GetObjectItem(route_item, "use_auth");
|
||||
cJSON *auth_username = cJSON_GetObjectItem(route_item, "username");
|
||||
cJSON *auth_password = cJSON_GetObjectItem(route_item, "password");
|
||||
|
||||
if (cJSON_IsTrue(use_auth) && cJSON_IsString(auth_username) && cJSON_IsString(auth_password)) {
|
||||
if (strlen(auth_username->valuestring) > 0 && strlen(auth_password->valuestring) > 0) {
|
||||
route->use_auth = 1;
|
||||
strncpy(route->username, auth_username->valuestring, sizeof(route->username) - 1);
|
||||
route->username[sizeof(route->username) - 1] = '\0';
|
||||
compute_password_hash(auth_password->valuestring, route->password_hash, sizeof(route->password_hash));
|
||||
}
|
||||
}
|
||||
|
||||
route->patches.rule_count = 0;
|
||||
cJSON *patch_obj = cJSON_GetObjectItem(route_item, "patch");
|
||||
if (cJSON_IsObject(patch_obj)) {
|
||||
cJSON *patch_item = NULL;
|
||||
cJSON_ArrayForEach(patch_item, patch_obj) {
|
||||
if (route->patches.rule_count >= MAX_PATCH_RULES) {
|
||||
log_info("Maximum patch rules reached for %s", route->hostname);
|
||||
break;
|
||||
}
|
||||
if (!patch_item->string) continue;
|
||||
size_t key_len = strlen(patch_item->string);
|
||||
if (key_len == 0 || key_len >= MAX_PATCH_KEY_SIZE) continue;
|
||||
|
||||
patch_rule_t *rule = &route->patches.rules[route->patches.rule_count];
|
||||
strncpy(rule->key, patch_item->string, MAX_PATCH_KEY_SIZE - 1);
|
||||
rule->key[MAX_PATCH_KEY_SIZE - 1] = '\0';
|
||||
rule->key_len = key_len;
|
||||
|
||||
if (cJSON_IsNull(patch_item)) {
|
||||
rule->is_null = 1;
|
||||
rule->value[0] = '\0';
|
||||
rule->value_len = 0;
|
||||
} else if (cJSON_IsString(patch_item)) {
|
||||
rule->is_null = 0;
|
||||
size_t val_len = strlen(patch_item->valuestring);
|
||||
if (val_len >= MAX_PATCH_VALUE_SIZE) val_len = MAX_PATCH_VALUE_SIZE - 1;
|
||||
strncpy(rule->value, patch_item->valuestring, MAX_PATCH_VALUE_SIZE - 1);
|
||||
rule->value[MAX_PATCH_VALUE_SIZE - 1] = '\0';
|
||||
rule->value_len = val_len;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
route->patches.rule_count++;
|
||||
}
|
||||
if (route->patches.rule_count > 0) {
|
||||
log_info("Loaded %d patch rules for %s", route->patches.rule_count, route->hostname);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("Route configured: %s -> %s:%d (SSL: %s, Rewrite Host: %s, Auth: %s)",
|
||||
route->hostname, route->upstream_host, route->upstream_port,
|
||||
route->use_ssl ? "yes" : "no", route->rewrite_host ? "yes" : "no",
|
||||
route->use_auth ? "yes" : "no");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
@@ -141,6 +298,14 @@ void config_free(void) {
|
||||
config_ref_dec(config);
|
||||
config = NULL;
|
||||
}
|
||||
|
||||
app_config_t *current = stale_configs_head;
|
||||
while (current) {
|
||||
app_config_t *next = current->next;
|
||||
config_ref_dec(current);
|
||||
current = next;
|
||||
}
|
||||
stale_configs_head = NULL;
|
||||
}
|
||||
|
||||
void config_create_default(const char *filename) {
|
||||
@@ -214,9 +379,17 @@ int config_check_file_changed(const char *filename) {
|
||||
int config_hot_reload(const char *filename) {
|
||||
log_info("Hot-reloading configuration from %s", filename);
|
||||
|
||||
char *json_string = config_read_file(filename);
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
if (!new_config) {
|
||||
log_error("Hot-reload: Failed to allocate memory for new config");
|
||||
return 0;
|
||||
}
|
||||
new_config->ref_count = 1;
|
||||
|
||||
char *json_string = read_file_to_string(filename);
|
||||
if (!json_string) {
|
||||
log_error("Hot-reload: Could not read config file");
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -225,27 +398,137 @@ int config_hot_reload(const char *filename) {
|
||||
const char *error_ptr = cJSON_GetErrorPtr();
|
||||
log_error("Hot-reload: JSON parse error: %s", error_ptr ? error_ptr : "unknown");
|
||||
free(json_string);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
free(json_string);
|
||||
|
||||
app_config_t *new_config = config_create_from_json(root);
|
||||
cJSON_Delete(root);
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
new_config->port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
|
||||
if (!new_config) {
|
||||
if (new_config->port < 1 || new_config->port > 65535) {
|
||||
log_error("Hot-reload: Invalid port number: %d", new_config->port);
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(filename, &st) == 0) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
cJSON *proxy_array = cJSON_GetObjectItem(root, "reverse_proxy");
|
||||
if (cJSON_IsArray(proxy_array)) {
|
||||
new_config->route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (new_config->route_count <= 0) {
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
new_config->routes = calloc(new_config->route_count, sizeof(route_config_t));
|
||||
if (!new_config->routes) {
|
||||
log_error("Hot-reload: Failed to allocate memory for routes");
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
cJSON *route_item;
|
||||
cJSON_ArrayForEach(route_item, proxy_array) {
|
||||
route_config_t *route = &new_config->routes[i];
|
||||
|
||||
cJSON *hostname = cJSON_GetObjectItem(route_item, "hostname");
|
||||
cJSON *upstream_host = cJSON_GetObjectItem(route_item, "upstream_host");
|
||||
cJSON *upstream_port = cJSON_GetObjectItem(route_item, "upstream_port");
|
||||
|
||||
if (!cJSON_IsString(hostname) || !cJSON_IsString(upstream_host) || !cJSON_IsNumber(upstream_port)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_valid_host(hostname->valuestring) || !is_valid_host(upstream_host->valuestring)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
strncpy(route->hostname, hostname->valuestring, sizeof(route->hostname) - 1);
|
||||
route->hostname[sizeof(route->hostname) - 1] = '\0';
|
||||
strncpy(route->upstream_host, upstream_host->valuestring, sizeof(route->upstream_host) - 1);
|
||||
route->upstream_host[sizeof(route->upstream_host) - 1] = '\0';
|
||||
route->upstream_port = upstream_port->valueint;
|
||||
|
||||
if (route->upstream_port < 1 || route->upstream_port > 65535) {
|
||||
continue;
|
||||
}
|
||||
|
||||
route->use_ssl = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "use_ssl"));
|
||||
route->rewrite_host = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "rewrite_host"));
|
||||
|
||||
route->use_auth = 0;
|
||||
route->username[0] = '\0';
|
||||
route->password_hash[0] = '\0';
|
||||
|
||||
cJSON *use_auth = cJSON_GetObjectItem(route_item, "use_auth");
|
||||
cJSON *auth_username = cJSON_GetObjectItem(route_item, "username");
|
||||
cJSON *auth_password = cJSON_GetObjectItem(route_item, "password");
|
||||
|
||||
if (cJSON_IsTrue(use_auth) && cJSON_IsString(auth_username) && cJSON_IsString(auth_password)) {
|
||||
if (strlen(auth_username->valuestring) > 0 && strlen(auth_password->valuestring) > 0) {
|
||||
route->use_auth = 1;
|
||||
strncpy(route->username, auth_username->valuestring, sizeof(route->username) - 1);
|
||||
route->username[sizeof(route->username) - 1] = '\0';
|
||||
compute_password_hash(auth_password->valuestring, route->password_hash, sizeof(route->password_hash));
|
||||
}
|
||||
}
|
||||
|
||||
route->patches.rule_count = 0;
|
||||
cJSON *patch_obj = cJSON_GetObjectItem(route_item, "patch");
|
||||
if (cJSON_IsObject(patch_obj)) {
|
||||
cJSON *patch_item = NULL;
|
||||
cJSON_ArrayForEach(patch_item, patch_obj) {
|
||||
if (route->patches.rule_count >= MAX_PATCH_RULES) break;
|
||||
if (!patch_item->string) continue;
|
||||
size_t key_len = strlen(patch_item->string);
|
||||
if (key_len == 0 || key_len >= MAX_PATCH_KEY_SIZE) continue;
|
||||
|
||||
patch_rule_t *rule = &route->patches.rules[route->patches.rule_count];
|
||||
strncpy(rule->key, patch_item->string, MAX_PATCH_KEY_SIZE - 1);
|
||||
rule->key[MAX_PATCH_KEY_SIZE - 1] = '\0';
|
||||
rule->key_len = key_len;
|
||||
|
||||
if (cJSON_IsNull(patch_item)) {
|
||||
rule->is_null = 1;
|
||||
rule->value[0] = '\0';
|
||||
rule->value_len = 0;
|
||||
} else if (cJSON_IsString(patch_item)) {
|
||||
rule->is_null = 0;
|
||||
size_t val_len = strlen(patch_item->valuestring);
|
||||
if (val_len >= MAX_PATCH_VALUE_SIZE) val_len = MAX_PATCH_VALUE_SIZE - 1;
|
||||
strncpy(rule->value, patch_item->valuestring, MAX_PATCH_VALUE_SIZE - 1);
|
||||
rule->value[MAX_PATCH_VALUE_SIZE - 1] = '\0';
|
||||
rule->value_len = val_len;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
route->patches.rule_count++;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("Hot-reload route: %s -> %s:%d (SSL: %s, Auth: %s, Patches: %d)",
|
||||
route->hostname, route->upstream_host, route->upstream_port,
|
||||
route->use_ssl ? "yes" : "no", route->use_auth ? "yes" : "no",
|
||||
route->patches.rule_count);
|
||||
i++;
|
||||
}
|
||||
new_config->route_count = i;
|
||||
}
|
||||
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
|
||||
app_config_t *old_config = config;
|
||||
config = new_config;
|
||||
|
||||
if (old_config) {
|
||||
old_config->next = stale_configs_head;
|
||||
stale_configs_head = old_config;
|
||||
}
|
||||
|
||||
log_info("Hot-reload complete: %d routes loaded", new_config->route_count);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Executable → Regular
@@ -1,198 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "config_parser.h"
|
||||
#include "logging.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
void config_compute_password_hash(const char *password, char *output, size_t output_size) {
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
if (!ctx) return;
|
||||
|
||||
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len = 0;
|
||||
|
||||
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
|
||||
EVP_DigestUpdate(ctx, password, strlen(password));
|
||||
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
for (unsigned int i = 0; i < hash_len && (i * 2 + 2) < output_size; i++) {
|
||||
snprintf(output + (i * 2), 3, "%02x", hash[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int config_is_valid_hostname(const char *hostname) {
|
||||
if (!hostname || strlen(hostname) == 0 || strlen(hostname) > 253) return 0;
|
||||
|
||||
const char *p = hostname;
|
||||
int label_len = 0;
|
||||
|
||||
while (*p) {
|
||||
char c = *p;
|
||||
if (c == '.') {
|
||||
if (label_len == 0) return 0;
|
||||
label_len = 0;
|
||||
} else if (isalnum((unsigned char)c) || c == '-' || c == '_') {
|
||||
label_len++;
|
||||
if (label_len > 63) return 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int config_is_valid_ip(const char *ip) {
|
||||
if (!ip) return 0;
|
||||
int dots = 0;
|
||||
int num = 0;
|
||||
int has_digit = 0;
|
||||
|
||||
while (*ip) {
|
||||
if (*ip == '.') {
|
||||
if (!has_digit || num > 255) return 0;
|
||||
dots++;
|
||||
num = 0;
|
||||
has_digit = 0;
|
||||
} else if (isdigit((unsigned char)*ip)) {
|
||||
num = num * 10 + (*ip - '0');
|
||||
has_digit = 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
ip++;
|
||||
}
|
||||
|
||||
return dots == 3 && has_digit && num <= 255;
|
||||
}
|
||||
|
||||
int config_is_valid_host(const char *host) {
|
||||
return config_is_valid_hostname(host) || config_is_valid_ip(host);
|
||||
}
|
||||
|
||||
char *config_read_file(const char *filename) {
|
||||
FILE *f = fopen(filename, "rb");
|
||||
if (!f) return NULL;
|
||||
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
long length = ftell(f);
|
||||
if (length < 0 || length > 1024 * 1024) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f, 0, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
char *buffer = malloc((size_t)length + 1);
|
||||
if (!buffer) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
size_t read_len = fread(buffer, 1, (size_t)length, f);
|
||||
buffer[read_len] = '\0';
|
||||
fclose(f);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int config_parse_route(cJSON *route_item, route_config_t *route, int index) {
|
||||
if (!route_item || !route) return 0;
|
||||
|
||||
cJSON *hostname = cJSON_GetObjectItem(route_item, "hostname");
|
||||
cJSON *upstream_host = cJSON_GetObjectItem(route_item, "upstream_host");
|
||||
cJSON *upstream_port = cJSON_GetObjectItem(route_item, "upstream_port");
|
||||
|
||||
if (!cJSON_IsString(hostname) || !cJSON_IsString(upstream_host) || !cJSON_IsNumber(upstream_port)) {
|
||||
log_debug("Invalid route configuration at index %d", index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!config_is_valid_host(hostname->valuestring)) {
|
||||
log_debug("Invalid hostname at index %d: %s", index, hostname->valuestring);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!config_is_valid_host(upstream_host->valuestring)) {
|
||||
log_debug("Invalid upstream_host at index %d: %s", index, upstream_host->valuestring);
|
||||
return 0;
|
||||
}
|
||||
|
||||
strncpy(route->hostname, hostname->valuestring, sizeof(route->hostname) - 1);
|
||||
route->hostname[sizeof(route->hostname) - 1] = '\0';
|
||||
strncpy(route->upstream_host, upstream_host->valuestring, sizeof(route->upstream_host) - 1);
|
||||
route->upstream_host[sizeof(route->upstream_host) - 1] = '\0';
|
||||
route->upstream_port = upstream_port->valueint;
|
||||
|
||||
if (route->upstream_port < 1 || route->upstream_port > 65535) {
|
||||
log_debug("Invalid upstream port for %s: %d", route->hostname, route->upstream_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
route->use_ssl = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "use_ssl"));
|
||||
route->rewrite_host = cJSON_IsTrue(cJSON_GetObjectItem(route_item, "rewrite_host"));
|
||||
|
||||
route->use_auth = 0;
|
||||
route->username[0] = '\0';
|
||||
route->password_hash[0] = '\0';
|
||||
|
||||
cJSON *use_auth = cJSON_GetObjectItem(route_item, "use_auth");
|
||||
cJSON *auth_username = cJSON_GetObjectItem(route_item, "username");
|
||||
cJSON *auth_password = cJSON_GetObjectItem(route_item, "password");
|
||||
|
||||
if (cJSON_IsTrue(use_auth) && cJSON_IsString(auth_username) && cJSON_IsString(auth_password)) {
|
||||
if (strlen(auth_username->valuestring) > 0 && strlen(auth_password->valuestring) > 0) {
|
||||
route->use_auth = 1;
|
||||
strncpy(route->username, auth_username->valuestring, sizeof(route->username) - 1);
|
||||
route->username[sizeof(route->username) - 1] = '\0';
|
||||
config_compute_password_hash(auth_password->valuestring, route->password_hash, sizeof(route->password_hash));
|
||||
}
|
||||
}
|
||||
|
||||
route->patches.rule_count = 0;
|
||||
cJSON *patch_obj = cJSON_GetObjectItem(route_item, "patch");
|
||||
if (cJSON_IsObject(patch_obj)) {
|
||||
cJSON *patch_item = NULL;
|
||||
cJSON_ArrayForEach(patch_item, patch_obj) {
|
||||
if (route->patches.rule_count >= MAX_PATCH_RULES) {
|
||||
log_info("Maximum patch rules reached for %s", route->hostname);
|
||||
break;
|
||||
}
|
||||
if (!patch_item->string) continue;
|
||||
size_t key_len = strlen(patch_item->string);
|
||||
if (key_len == 0 || key_len >= MAX_PATCH_KEY_SIZE) continue;
|
||||
|
||||
patch_rule_t *rule = &route->patches.rules[route->patches.rule_count];
|
||||
strncpy(rule->key, patch_item->string, MAX_PATCH_KEY_SIZE - 1);
|
||||
rule->key[MAX_PATCH_KEY_SIZE - 1] = '\0';
|
||||
rule->key_len = key_len;
|
||||
|
||||
if (cJSON_IsNull(patch_item)) {
|
||||
rule->is_null = 1;
|
||||
rule->value[0] = '\0';
|
||||
rule->value_len = 0;
|
||||
} else if (cJSON_IsString(patch_item)) {
|
||||
rule->is_null = 0;
|
||||
size_t val_len = strlen(patch_item->valuestring);
|
||||
if (val_len >= MAX_PATCH_VALUE_SIZE) val_len = MAX_PATCH_VALUE_SIZE - 1;
|
||||
strncpy(rule->value, patch_item->valuestring, MAX_PATCH_VALUE_SIZE - 1);
|
||||
rule->value[MAX_PATCH_VALUE_SIZE - 1] = '\0';
|
||||
rule->value_len = val_len;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
route->patches.rule_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_CONFIG_PARSER_H
|
||||
#define RPROXY_CONFIG_PARSER_H
|
||||
|
||||
#include "types.h"
|
||||
#include "../cJSON.h"
|
||||
|
||||
int config_parse_route(cJSON *route_item, route_config_t *route, int index);
|
||||
int config_is_valid_hostname(const char *hostname);
|
||||
int config_is_valid_ip(const char *ip);
|
||||
int config_is_valid_host(const char *host);
|
||||
void config_compute_password_hash(const char *password, char *output, size_t output_size);
|
||||
char *config_read_file(const char *filename);
|
||||
|
||||
#endif
|
||||
+864
-345
File diff suppressed because it is too large
Load Diff
Executable → Regular
-4
@@ -6,9 +6,7 @@
|
||||
|
||||
extern connection_t connections[MAX_FDS];
|
||||
extern int epoll_fd;
|
||||
extern time_t cached_time;
|
||||
|
||||
void connection_update_cached_time(void);
|
||||
void connection_init_all(void);
|
||||
void connection_setup_listener(int port);
|
||||
void connection_accept(int listener_fd);
|
||||
@@ -18,8 +16,6 @@ void connection_cleanup_idle(void);
|
||||
|
||||
int connection_set_non_blocking(int fd);
|
||||
void connection_set_tcp_keepalive(int fd);
|
||||
void connection_set_tcp_nodelay(int fd);
|
||||
void connection_optimize_socket(int fd, int is_upstream);
|
||||
void connection_add_to_epoll(int fd, uint32_t events);
|
||||
void connection_modify_epoll(int fd, uint32_t events);
|
||||
|
||||
|
||||
Executable → Regular
+257
-212
@@ -19,122 +19,275 @@ static const char *DASHBOARD_HTML =
|
||||
" <style>\n"
|
||||
" * { margin: 0; padding: 0; box-sizing: border-box; }\n"
|
||||
" body { font-family: -apple-system, system-ui, sans-serif; background: #000; color: #fff; padding: 20px; }\n"
|
||||
" .header { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); margin-bottom: 30px; gap: 15px; }\n"
|
||||
" .metric { text-align: center; background: #111; border-radius: 8px; padding: 12px; }\n"
|
||||
" .metric-value { font-size: 28px; font-weight: 300; }\n"
|
||||
" .metric-label { font-size: 11px; opacity: 0.7; text-transform: uppercase; margin-top: 4px; }\n"
|
||||
" .metric-value.health { color: #2ecc71; }\n"
|
||||
" .metric-value.warning { color: #f39c12; }\n"
|
||||
" .metric-value.danger { color: #e74c3c; }\n"
|
||||
" .charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; margin-bottom: 20px; }\n"
|
||||
" .chart-container { background: #111; border-radius: 8px; padding: 20px; height: 220px; position: relative; }\n"
|
||||
" .chart-container.tall { height: 280px; }\n"
|
||||
" .chart-title { position: absolute; top: 10px; left: 20px; font-size: 13px; opacity: 0.7; z-index: 10; }\n"
|
||||
" .header { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); margin-bottom: 30px; gap: 20px; }\n"
|
||||
" .metric { text-align: center; background: #000; border-radius: 8px; padding: 15px; }\n"
|
||||
" .metric-value { font-size: 36px; font-weight: 300; }\n"
|
||||
" .metric-label { font-size: 14px; opacity: 0.7; text-transform: uppercase; margin-top: 5px; }\n"
|
||||
" .chart-container { background: #000; border-radius: 8px; padding: 20px; margin-bottom: 20px; height: 250px; position: relative; }\n"
|
||||
" .chart-title { position: absolute; top: 10px; left: 20px; font-size: 14px; opacity: 0.7; z-index: 10; }\n"
|
||||
" canvas { width: 100% !important; height: 100% !important; }\n"
|
||||
" .process-table { background: #111; border-radius: 8px; padding: 20px; overflow-x: auto; }\n"
|
||||
" table { width: 100%; border-collapse: collapse; font-size: 13px; }\n"
|
||||
" th, td { padding: 8px 10px; text-align: left; border-bottom: 1px solid #2a2e3e; }\n"
|
||||
" th { font-weight: 500; opacity: 0.7; white-space: nowrap; }\n"
|
||||
" .legend { position: absolute; top: 10px; right: 20px; display: flex; gap: 15px; font-size: 11px; z-index: 10; }\n"
|
||||
" .legend-item { display: flex; align-items: center; gap: 4px; }\n"
|
||||
" .legend-color { width: 10px; height: 10px; border-radius: 2px; }\n"
|
||||
" .vhost-chart-container { height: 180px; background: #111; border-radius: 8px; position: relative; padding: 15px; margin-bottom: 10px; }\n"
|
||||
" .status-indicator { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }\n"
|
||||
" .status-ok { background: #2ecc71; }\n"
|
||||
" .status-warn { background: #f39c12; }\n"
|
||||
" .status-error { background: #e74c3c; }\n"
|
||||
" .process-table { background: #000; border-radius: 8px; padding: 20px; }\n"
|
||||
" table { width: 100%; border-collapse: collapse; }\n"
|
||||
" th, td { padding: 10px; text-align: left; border-bottom: 1px solid #2a2e3e; }\n"
|
||||
" th { font-weight: 500; opacity: 0.7; }\n"
|
||||
" .legend { position: absolute; top: 10px; right: 20px; display: flex; gap: 20px; font-size: 12px; z-index: 10; }\n"
|
||||
" .legend-item { display: flex; align-items: center; gap: 5px; }\n"
|
||||
" .legend-color { width: 12px; height: 12px; border-radius: 2px; }\n"
|
||||
" .vhost-chart-container { height: 200px; background: #000; border-radius: 8px; position: relative; padding: 20px; margin-bottom: 10px; }\n"
|
||||
" .load-values { display: flex; gap: 10px; font-size: 12px; opacity: 0.8; }\n"
|
||||
" </style>\n"
|
||||
" <script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
" <div class=\"header\">\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value health\" id=\"healthScore\">100</div><div class=\"metric-label\">Health</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"rps\">0</div><div class=\"metric-label\">Req/sec</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"p50\">0</div><div class=\"metric-label\">p50 ms</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"p99\">0</div><div class=\"metric-label\">p99 ms</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"errorRate\">0%</div><div class=\"metric-label\">Errors</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"connections\">0</div><div class=\"metric-label\">Conns</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"cpu\">0</div><div class=\"metric-label\">CPU</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"memory\">0</div><div class=\"metric-label\">Memory</div></div>\n"
|
||||
" <div class=\"metric\"><div class=\"metric-value\" id=\"uptime\">0s</div><div class=\"metric-label\">Uptime</div></div>\n"
|
||||
" <div class=\"metric\">\n"
|
||||
" <div class=\"metric-value\" id=\"connections\">0</div>\n"
|
||||
" <div class=\"metric-label\">Connections</div>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"metric\">\n"
|
||||
" <div class=\"metric-value\" id=\"memory\">0</div>\n"
|
||||
" <div class=\"metric-label\">Memory</div>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"metric\">\n"
|
||||
" <div class=\"metric-value\" id=\"cpu\">0</div>\n"
|
||||
" <div class=\"metric-label\">CPU %</div>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"metric\">\n"
|
||||
" <div class=\"metric-value\" id=\"load1\">0.00</div>\n"
|
||||
" <div class=\"metric-label\">Load 1m</div>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"metric\">\n"
|
||||
" <div class=\"metric-value\" id=\"load5\">0.00</div>\n"
|
||||
" <div class=\"metric-label\">Load 5m</div>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"metric\">\n"
|
||||
" <div class=\"metric-value\" id=\"load15\">0.00</div>\n"
|
||||
" <div class=\"metric-label\">Load 15m</div>\n"
|
||||
" </div>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"charts-grid\">\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">Latency Distribution</div><canvas id=\"latencyHistChart\"></canvas></div>\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">Status Codes</div><canvas id=\"statusChart\"></canvas></div>\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">HTTP Methods</div><canvas id=\"methodsChart\"></canvas></div>\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">Efficiency</div><canvas id=\"efficiencyChart\"></canvas></div>\n"
|
||||
"\n"
|
||||
" <div class=\"chart-container\">\n"
|
||||
" <div class=\"chart-title\">CPU Usage</div>\n"
|
||||
" <canvas id=\"cpuChart\"></canvas>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"charts-grid\">\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">CPU Usage</div><canvas id=\"cpuChart\"></canvas></div>\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">Memory Usage</div><canvas id=\"memChart\"></canvas></div>\n"
|
||||
"\n"
|
||||
" <div class=\"chart-container\">\n"
|
||||
" <div class=\"chart-title\">Memory Usage</div>\n"
|
||||
" <canvas id=\"memChart\"></canvas>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"charts-grid\">\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">Network I/O</div>\n"
|
||||
" <div class=\"legend\"><div class=\"legend-item\"><div class=\"legend-color\" style=\"background:#3498db\"></div><span>RX</span></div><div class=\"legend-item\"><div class=\"legend-color\" style=\"background:#2ecc71\"></div><span>TX</span></div></div>\n"
|
||||
" <canvas id=\"netChart\"></canvas></div>\n"
|
||||
" <div class=\"chart-container\"><div class=\"chart-title\">Disk I/O</div>\n"
|
||||
" <div class=\"legend\"><div class=\"legend-item\"><div class=\"legend-color\" style=\"background:#9b59b6\"></div><span>Read</span></div><div class=\"legend-item\"><div class=\"legend-color\" style=\"background:#e67e22\"></div><span>Write</span></div></div>\n"
|
||||
" <canvas id=\"diskChart\"></canvas></div>\n"
|
||||
"\n"
|
||||
" <div class=\"chart-container\">\n"
|
||||
" <div class=\"chart-title\">Network I/O</div>\n"
|
||||
" <div class=\"legend\">\n"
|
||||
" <div class=\"legend-item\"><div class=\"legend-color\" style=\"background: #3498db\"></div><span>RX</span></div>\n"
|
||||
" <div class=\"legend-item\"><div class=\"legend-color\" style=\"background: #2ecc71\"></div><span>TX</span></div>\n"
|
||||
" </div>\n"
|
||||
" <canvas id=\"netChart\"></canvas>\n"
|
||||
" </div>\n"
|
||||
" <div class=\"chart-container tall\" style=\"margin-bottom:20px;\"><div class=\"chart-title\">Load Average</div>\n"
|
||||
" <div class=\"legend\"><div class=\"legend-item\"><div class=\"legend-color\" style=\"background:#e74c3c\"></div><span>1m</span></div><div class=\"legend-item\"><div class=\"legend-color\" style=\"background:#f39c12\"></div><span>5m</span></div><div class=\"legend-item\"><div class=\"legend-color\" style=\"background:#3498db\"></div><span>15m</span></div></div>\n"
|
||||
" <canvas id=\"loadChart\"></canvas></div>\n"
|
||||
" <div class=\"process-table\"><table><thead><tr><th>Virtual Host</th><th>RPS</th><th>Total</th><th>p50</th><th>p99</th><th>2xx</th><th>4xx</th><th>5xx</th><th>Err%</th><th>Sent</th><th>Recv</th></tr></thead><tbody id=\"processTable\"></tbody></table></div>\n"
|
||||
"\n"
|
||||
" <div class=\"chart-container\">\n"
|
||||
" <div class=\"chart-title\">Disk I/O</div>\n"
|
||||
" <div class=\"legend\">\n"
|
||||
" <div class=\"legend-item\"><div class=\"legend-color\" style=\"background: #9b59b6\"></div><span>Read</span></div>\n"
|
||||
" <div class=\"legend-item\"><div class=\"legend-color\" style=\"background: #e67e22\"></div><span>Write</span></div>\n"
|
||||
" </div>\n"
|
||||
" <canvas id=\"diskChart\"></canvas>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <div class=\"chart-container\">\n"
|
||||
" <div class=\"chart-title\">Load Average</div>\n"
|
||||
" <div class=\"legend\">\n"
|
||||
" <div class=\"legend-item\"><div class=\"legend-color\" style=\"background: #e74c3c\"></div><span>1 min</span></div>\n"
|
||||
" <div class=\"legend-item\"><div class=\"legend-color\" style=\"background: #f39c12\"></div><span>5 min</span></div>\n"
|
||||
" <div class=\"legend-item\"><div class=\"legend-color\" style=\"background: #3498db\"></div><span>15 min</span></div>\n"
|
||||
" </div>\n"
|
||||
" <canvas id=\"loadChart\"></canvas>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <div class=\"process-table\">\n"
|
||||
" <table>\n"
|
||||
" <thead>\n"
|
||||
" <tr>\n"
|
||||
" <th>Virtual Host</th>\n"
|
||||
" <th>HTTP Req</th>\n"
|
||||
" <th>WS Req</th>\n"
|
||||
" <th>Total Req</th>\n"
|
||||
" <th>Avg Resp (ms)</th>\n"
|
||||
" <th>Sent</th>\n"
|
||||
" <th>Received</th>\n"
|
||||
" </tr>\n"
|
||||
" </thead>\n"
|
||||
" <tbody id=\"processTable\"></tbody>\n"
|
||||
" </table>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <script>\n"
|
||||
" const formatTime = v => { const s=Math.abs(Math.round(v/1000)); if(s===0)return'now'; const m=Math.floor(s/60),ss=s%60; return`-${m>0?m+'m ':''}${ss}s`; };\n"
|
||||
" const formatSize = v => { if(v>=1048576)return(v/1048576).toFixed(1)+' GB/s'; if(v>=1024)return(v/1024).toFixed(1)+' MB/s'; return v.toFixed(0)+' KB/s'; };\n"
|
||||
" const formatDisk = v => v>=1024?(v/1024).toFixed(1)+' GB/s':v.toFixed(1)+' MB/s';\n"
|
||||
" const formatBytes = b => { if(b===0)return'0 B'; const k=1024,s=['B','KB','MB','GB','TB'],i=Math.floor(Math.log(b)/Math.log(k)); return(b/Math.pow(k,i)).toFixed(1)+' '+s[i]; };\n"
|
||||
" const formatUptime = s => { const d=Math.floor(s/86400),h=Math.floor((s%86400)/3600),m=Math.floor((s%3600)/60); return d>0?d+'d '+h+'h':h>0?h+'h '+m+'m':m+'m'; };\n"
|
||||
" const formatNum = n => { if(n===null||n===undefined)return'0'; n=parseFloat(n); if(isNaN(n))return'0'; if(n>=1e9)return(n/1e9).toFixed(1)+'B'; if(n>=1e6)return(n/1e6).toFixed(1)+'M'; if(n>=1e3)return(n/1e3).toFixed(1)+'K'; return n.toFixed(0); };\n"
|
||||
" const formatMs = v => { if(v>=1000)return(v/1000).toFixed(1)+'s'; return v.toFixed(0)+'ms'; };\n"
|
||||
" const baseOpts = (sec,yCb) => ({responsive:true,maintainAspectRatio:false,animation:false,layout:{padding:{top:30}},interaction:{mode:'nearest',axis:'x',intersect:false},scales:{x:{type:'linear',display:true,grid:{color:'#2a2e3e'},ticks:{color:'#666',maxTicksLimit:7,callback:formatTime},min:-sec*1000,max:0},y:{display:true,grid:{color:'#2a2e3e'},ticks:{color:'#666',beginAtZero:true,callback:yCb}}},plugins:{legend:{display:false}},elements:{point:{radius:0},line:{borderWidth:2,tension:0.4,fill:true}}});\n"
|
||||
" const cpuChart = new Chart(document.getElementById('cpuChart'),{type:'line',data:{datasets:[{data:[],borderColor:'#f39c12',backgroundColor:'rgba(243,156,18,0.1)'}]},options:{...baseOpts(300,v=>v+'%'),scales:{...baseOpts(300).scales,y:{...baseOpts(300).scales.y,max:100}}}});\n"
|
||||
" const memChart = new Chart(document.getElementById('memChart'),{type:'line',data:{datasets:[{data:[],borderColor:'#e74c3c',backgroundColor:'rgba(231,76,60,0.1)'}]},options:baseOpts(300,v=>v.toFixed(1)+' GB')});\n"
|
||||
" const netChart = new Chart(document.getElementById('netChart'),{type:'line',data:{datasets:[{data:[],borderColor:'#3498db',backgroundColor:'rgba(52,152,219,0.1)'},{data:[],borderColor:'#2ecc71',backgroundColor:'rgba(46,204,113,0.1)'}]},options:baseOpts(300,formatSize)});\n"
|
||||
" const diskChart = new Chart(document.getElementById('diskChart'),{type:'line',data:{datasets:[{data:[],borderColor:'#9b59b6',backgroundColor:'rgba(155,89,182,0.1)'},{data:[],borderColor:'#e67e22',backgroundColor:'rgba(230,126,34,0.1)'}]},options:baseOpts(300,formatDisk)});\n"
|
||||
" const loadChart = new Chart(document.getElementById('loadChart'),{type:'line',data:{datasets:[{data:[],borderColor:'#e74c3c',backgroundColor:'rgba(231,76,60,0.1)'},{data:[],borderColor:'#f39c12',backgroundColor:'rgba(243,156,18,0.1)'},{data:[],borderColor:'#3498db',backgroundColor:'rgba(52,152,219,0.1)'}]},options:baseOpts(300,v=>v.toFixed(2))});\n"
|
||||
" const latencyHistChart = new Chart(document.getElementById('latencyHistChart'),{type:'bar',data:{labels:[],datasets:[{data:[],backgroundColor:v=>{const i=v.dataIndex;return i<4?'#2ecc71':i<8?'#f39c12':'#e74c3c';}}]},options:{responsive:true,maintainAspectRatio:false,animation:false,layout:{padding:{top:30}},plugins:{legend:{display:false}},scales:{x:{grid:{display:false},ticks:{color:'#666',maxRotation:45}},y:{grid:{color:'#2a2e3e'},ticks:{color:'#666'}}}}});\n"
|
||||
" const statusChart = new Chart(document.getElementById('statusChart'),{type:'doughnut',data:{labels:['2xx','3xx','4xx','5xx'],datasets:[{data:[0,0,0,0],backgroundColor:['#2ecc71','#3498db','#f39c12','#e74c3c']}]},options:{responsive:true,maintainAspectRatio:false,animation:false,layout:{padding:{top:30}},plugins:{legend:{display:true,position:'right',labels:{color:'#fff',font:{size:11}}}}}});\n"
|
||||
" const methodsChart = new Chart(document.getElementById('methodsChart'),{type:'bar',data:{labels:['GET','POST','PUT','DEL','PATCH','HEAD','OPT'],datasets:[{data:[0,0,0,0,0,0,0],backgroundColor:'#3498db'}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,animation:false,layout:{padding:{top:30}},plugins:{legend:{display:false}},scales:{x:{grid:{color:'#2a2e3e'},ticks:{color:'#666'}},y:{grid:{display:false},ticks:{color:'#666'}}}}});\n"
|
||||
" const efficiencyChart = new Chart(document.getElementById('efficiencyChart'),{type:'doughnut',data:{labels:['Zero-Copy','Buffered'],datasets:[{data:[0,0],backgroundColor:['#2ecc71','#9b59b6']}]},options:{responsive:true,maintainAspectRatio:false,animation:false,layout:{padding:{top:30}},plugins:{legend:{display:true,position:'right',labels:{color:'#fff',font:{size:11}}}}}});\n"
|
||||
" window.vhostCharts={}; let prevNames=[];\n"
|
||||
" async function updateStats(){\n"
|
||||
" try{\n"
|
||||
" const r=await fetch('/rproxy/api/stats'),d=await r.json();\n"
|
||||
" const hs=d.current.health_score||100; document.getElementById('healthScore').textContent=hs.toFixed(0);\n"
|
||||
" const hEl=document.getElementById('healthScore'); hEl.className='metric-value '+(hs>=80?'health':hs>=50?'warning':'danger');\n"
|
||||
" document.getElementById('rps').textContent=formatNum(d.current.requests_per_second||0);\n"
|
||||
" document.getElementById('p50').textContent=formatMs(d.latency?.p50_ms||0);\n"
|
||||
" document.getElementById('p99').textContent=formatMs(d.latency?.p99_ms||0);\n"
|
||||
" document.getElementById('errorRate').textContent=((d.current.error_rate_1m||0)*100).toFixed(1)+'%';\n"
|
||||
" document.getElementById('connections').textContent=formatNum(d.current.active_connections);\n"
|
||||
" document.getElementById('cpu').textContent=d.current.cpu_percent+'%';\n"
|
||||
" document.getElementById('memory').textContent=d.current.memory_gb+'GB';\n"
|
||||
" document.getElementById('uptime').textContent=formatUptime(d.current.uptime_seconds||0);\n"
|
||||
" cpuChart.data.datasets[0].data=d.cpu_history; memChart.data.datasets[0].data=d.memory_history;\n"
|
||||
" netChart.data.datasets[0].data=d.network_rx_history; netChart.data.datasets[1].data=d.network_tx_history;\n"
|
||||
" diskChart.data.datasets[0].data=d.disk_read_history; diskChart.data.datasets[1].data=d.disk_write_history;\n"
|
||||
" loadChart.data.datasets[0].data=d.load1_history; loadChart.data.datasets[1].data=d.load5_history; loadChart.data.datasets[2].data=d.load15_history;\n"
|
||||
" [cpuChart,memChart,netChart,diskChart,loadChart].forEach(c=>c.update('none'));\n"
|
||||
" if(d.latency?.histogram){latencyHistChart.data.labels=d.latency.bucket_labels||[]; latencyHistChart.data.datasets[0].data=d.latency.histogram; latencyHistChart.update('none');}\n"
|
||||
" if(d.status_codes){statusChart.data.datasets[0].data=[d.status_codes['2xx']||0,d.status_codes['3xx']||0,d.status_codes['4xx']||0,d.status_codes['5xx']||0]; statusChart.update('none');}\n"
|
||||
" if(d.methods){methodsChart.data.datasets[0].data=[d.methods.GET||0,d.methods.POST||0,d.methods.PUT||0,d.methods.DELETE||0,d.methods.PATCH||0,d.methods.HEAD||0,d.methods.OPTIONS||0]; methodsChart.update('none');}\n"
|
||||
" if(d.efficiency){efficiencyChart.data.datasets[0].data=[d.efficiency.splice_transfers||0,d.efficiency.buffer_transfers||0]; efficiencyChart.update('none');}\n"
|
||||
" const tbody=document.getElementById('processTable'),names=d.processes.map(p=>p.name).join(',');\n"
|
||||
" if(names!==prevNames.join(',')){ Object.values(window.vhostCharts).forEach(c=>c.destroy()); window.vhostCharts={}; tbody.innerHTML=''; prevNames=d.processes.map(p=>p.name); }\n"
|
||||
" d.processes.forEach((p,i)=>{\n"
|
||||
" let row=tbody.children[i*2]; if(!row){row=document.createElement('tr'); tbody.appendChild(row);}\n"
|
||||
" const errP=(p.error_rate*100).toFixed(1); const cls=parseFloat(errP)>5?'status-error':parseFloat(errP)>1?'status-warn':'status-ok';\n"
|
||||
" row.innerHTML=`<td><span class='status-indicator ${cls}'></span>${p.name}</td><td>${formatNum(p.rps||0)}</td><td>${formatNum(p.total_requests)}</td><td>${formatMs(p.latency_p50||0)}</td><td>${formatMs(p.latency_p99||0)}</td><td>${formatNum(p.status_2xx||0)}</td><td>${formatNum(p.status_4xx||0)}</td><td>${formatNum(p.status_5xx||0)}</td><td>${errP}%</td><td>${formatBytes(p.bytes_sent)}</td><td>${formatBytes(p.bytes_recv)}</td>`;\n"
|
||||
" let chartRow=tbody.children[i*2+1]; if(!chartRow){chartRow=document.createElement('tr'); chartRow.innerHTML=`<td colspan='11' style='padding:10px 0;border:none;'><div class='vhost-chart-container'><div class='chart-title'>Throughput - ${p.name}</div><canvas id='vc${i}'></canvas></div></td>`; tbody.appendChild(chartRow);}\n"
|
||||
" const cid='vc'+i,cv=document.getElementById(cid); if(!cv)return;\n"
|
||||
" if(!window.vhostCharts[cid]){const cols=[{b:'#3498db',bg:'rgba(52,152,219,0.1)'},{b:'#2ecc71',bg:'rgba(46,204,113,0.1)'},{b:'#f39c12',bg:'rgba(243,156,18,0.1)'},{b:'#e74c3c',bg:'rgba(231,76,60,0.1)'}],c=cols[i%4]; window.vhostCharts[cid]=new Chart(cv,{type:'line',data:{datasets:[{data:p.throughput_history||[],borderColor:c.b,backgroundColor:c.bg}]},options:baseOpts(60,formatSize)});}\n"
|
||||
" else{window.vhostCharts[cid].data.datasets[0].data=p.throughput_history||[]; window.vhostCharts[cid].update('none');}\n"
|
||||
" });\n"
|
||||
" }catch(e){console.error('Stats error:',e);}\n"
|
||||
" const formatTimeTick = (value) => {\n"
|
||||
" const seconds = Math.abs(Math.round(value / 1000));\n"
|
||||
" if (seconds === 0) return 'now';\n"
|
||||
" const m = Math.floor(seconds / 60);\n"
|
||||
" const s = seconds % 60;\n"
|
||||
" return `-${m > 0 ? `${m}m ` : ''}${s}s`;\n"
|
||||
" };\n"
|
||||
"\n"
|
||||
" const formatSizeTick = (value) => {\n"
|
||||
" if (value >= 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(1)} GB/s`;\n"
|
||||
" if (value >= 1024) return `${(value / 1024).toFixed(1)} MB/s`;\n"
|
||||
" return `${value.toFixed(0)} KB/s`;\n"
|
||||
" };\n"
|
||||
"\n"
|
||||
" const formatDiskTick = (value) => {\n"
|
||||
" if (value >= 1024) return `${(value / 1024).toFixed(1)} GB/s`;\n"
|
||||
" return `${value.toFixed(1)} MB/s`;\n"
|
||||
" };\n"
|
||||
"\n"
|
||||
" const formatBytes = (bytes) => {\n"
|
||||
" if (bytes === 0) return '0 B';\n"
|
||||
" const k = 1024;\n"
|
||||
" const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];\n"
|
||||
" const i = Math.floor(Math.log(bytes) / Math.log(k));\n"
|
||||
" return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n"
|
||||
" };\n"
|
||||
"\n"
|
||||
" const createBaseChartOptions = (historySeconds, yTickCallback) => ({\n"
|
||||
" responsive: true,\n"
|
||||
" maintainAspectRatio: false,\n"
|
||||
" animation: false,\n"
|
||||
" layout: { padding: { top: 30 } },\n"
|
||||
" interaction: { mode: 'nearest', axis: 'x', intersect: false },\n"
|
||||
" scales: {\n"
|
||||
" x: { type: 'linear', display: true, grid: { color: '#2a2e3e' }, ticks: { color: '#666', maxTicksLimit: 7, callback: formatTimeTick }, min: -historySeconds * 1000, max: 0 },\n"
|
||||
" y: { display: true, grid: { color: '#2a2e3e' }, ticks: { color: '#666', beginAtZero: true, callback: yTickCallback } }\n"
|
||||
" },\n"
|
||||
" plugins: { legend: { display: false }, tooltip: { displayColors: false } },\n"
|
||||
" elements: { point: { radius: 0 }, line: { borderWidth: 2, tension: 0.4, fill: true } }\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const cpuChart = new Chart(document.getElementById('cpuChart'), {\n"
|
||||
" type: 'line',\n"
|
||||
" data: { datasets: [{ label: 'CPU %', data: [], borderColor: '#f39c12', backgroundColor: 'rgba(243, 156, 18, 0.1)' }] },\n"
|
||||
" options: { ...createBaseChartOptions(300, v => `${v}%`), scales: { ...createBaseChartOptions(300).scales, y: { ...createBaseChartOptions(300).scales.y, max: 100, ticks: { ...createBaseChartOptions(300).scales.y.ticks, callback: v => `${v}%` } } } }\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const memChart = new Chart(document.getElementById('memChart'), {\n"
|
||||
" type: 'line',\n"
|
||||
" data: { datasets: [{ label: 'Memory GB', data: [], borderColor: '#e74c3c', backgroundColor: 'rgba(231, 76, 60, 0.1)' }] },\n"
|
||||
" options: createBaseChartOptions(300, v => `${v.toFixed(2)} GiB`)\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const netChart = new Chart(document.getElementById('netChart'), {\n"
|
||||
" type: 'line',\n"
|
||||
" data: {\n"
|
||||
" datasets: [\n"
|
||||
" { label: 'RX KB/s', data: [], borderColor: '#3498db', backgroundColor: 'rgba(52, 152, 219, 0.1)' },\n"
|
||||
" { label: 'TX KB/s', data: [], borderColor: '#2ecc71', backgroundColor: 'rgba(46, 204, 113, 0.1)' }\n"
|
||||
" ]\n"
|
||||
" },\n"
|
||||
" options: createBaseChartOptions(300, formatSizeTick)\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const diskChart = new Chart(document.getElementById('diskChart'), {\n"
|
||||
" type: 'line',\n"
|
||||
" data: {\n"
|
||||
" datasets: [\n"
|
||||
" { label: 'Read MB/s', data: [], borderColor: '#9b59b6', backgroundColor: 'rgba(155, 89, 182, 0.1)' },\n"
|
||||
" { label: 'Write MB/s', data: [], borderColor: '#e67e22', backgroundColor: 'rgba(230, 126, 34, 0.1)' }\n"
|
||||
" ]\n"
|
||||
" },\n"
|
||||
" options: createBaseChartOptions(300, formatDiskTick)\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" const loadChart = new Chart(document.getElementById('loadChart'), {\n"
|
||||
" type: 'line',\n"
|
||||
" data: {\n"
|
||||
" datasets: [\n"
|
||||
" { label: 'Load 1m', data: [], borderColor: '#e74c3c', backgroundColor: 'rgba(231, 76, 60, 0.1)' },\n"
|
||||
" { label: 'Load 5m', data: [], borderColor: '#f39c12', backgroundColor: 'rgba(243, 156, 18, 0.1)' },\n"
|
||||
" { label: 'Load 15m', data: [], borderColor: '#3498db', backgroundColor: 'rgba(52, 152, 219, 0.1)' }\n"
|
||||
" ]\n"
|
||||
" },\n"
|
||||
" options: createBaseChartOptions(300, v => v.toFixed(2))\n"
|
||||
" });\n"
|
||||
"\n"
|
||||
" window.vhostCharts = {};\n"
|
||||
" let prevProcessNames = [];\n"
|
||||
"\n"
|
||||
" async function updateStats() {\n"
|
||||
" try {\n"
|
||||
" const response = await fetch('/rproxy/api/stats');\n"
|
||||
" const data = await response.json();\n"
|
||||
"\n"
|
||||
" document.getElementById('connections').textContent = data.current.active_connections;\n"
|
||||
" document.getElementById('memory').textContent = data.current.memory_gb + ' GiB';\n"
|
||||
" document.getElementById('cpu').textContent = data.current.cpu_percent + '%';\n"
|
||||
" document.getElementById('load1').textContent = data.current.load_1m.toFixed(2);\n"
|
||||
" document.getElementById('load5').textContent = data.current.load_5m.toFixed(2);\n"
|
||||
" document.getElementById('load15').textContent = data.current.load_15m.toFixed(2);\n"
|
||||
"\n"
|
||||
" cpuChart.data.datasets[0].data = data.cpu_history;\n"
|
||||
" memChart.data.datasets[0].data = data.memory_history;\n"
|
||||
" netChart.data.datasets[0].data = data.network_rx_history;\n"
|
||||
" netChart.data.datasets[1].data = data.network_tx_history;\n"
|
||||
" diskChart.data.datasets[0].data = data.disk_read_history;\n"
|
||||
" diskChart.data.datasets[1].data = data.disk_write_history;\n"
|
||||
" loadChart.data.datasets[0].data = data.load1_history;\n"
|
||||
" loadChart.data.datasets[1].data = data.load5_history;\n"
|
||||
" loadChart.data.datasets[2].data = data.load15_history;\n"
|
||||
"\n"
|
||||
" cpuChart.update('none');\n"
|
||||
" memChart.update('none');\n"
|
||||
" netChart.update('none');\n"
|
||||
" diskChart.update('none');\n"
|
||||
" loadChart.update('none');\n"
|
||||
"\n"
|
||||
" const tbody = document.getElementById('processTable');\n"
|
||||
" const processNames = data.processes.map(p => p.name).join(',');\n"
|
||||
" if (processNames !== prevProcessNames.join(',')) {\n"
|
||||
" Object.values(window.vhostCharts).forEach(chart => chart.destroy());\n"
|
||||
" window.vhostCharts = {};\n"
|
||||
" tbody.innerHTML = '';\n"
|
||||
" data.processes.forEach((p, index) => {\n"
|
||||
" const colors = ['#3498db', '#2ecc71', '#f39c12', '#e74c3c', '#9b59b6', '#1abc9c'];\n"
|
||||
" const chartColor = colors[index % colors.length];\n"
|
||||
" const mainRow = document.createElement('tr');\n"
|
||||
" mainRow.innerHTML = `<td style='color: ${chartColor}'>${p.name}</td><td>${p.http_requests}</td><td>${p.websocket_requests}</td><td>${p.total_requests}</td><td>${p.avg_request_time_ms.toFixed(2)}</td><td>${formatBytes(p.bytes_sent)}</td><td>${formatBytes(p.bytes_recv)}</td>`;\n"
|
||||
" tbody.appendChild(mainRow);\n"
|
||||
" \n"
|
||||
" const chartRow = document.createElement('tr');\n"
|
||||
" chartRow.innerHTML = `<td colspan='7' style='padding: 10px 0; border: none;'><div class='vhost-chart-container'><div class='chart-title'>Live Throughput - ${p.name}</div><canvas id='vhostChart${index}'></canvas></div></td>`;\n"
|
||||
" tbody.appendChild(chartRow);\n"
|
||||
" });\n"
|
||||
" prevProcessNames = data.processes.map(p => p.name);\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" data.processes.forEach((p, index) => {\n"
|
||||
" const chartId = `vhostChart${index}`;\n"
|
||||
" const canvas = document.getElementById(chartId);\n"
|
||||
" if (!canvas) return;\n"
|
||||
" if (!window.vhostCharts[chartId]) {\n"
|
||||
" const colors = [\n"
|
||||
" { border: '#3498db', bg: 'rgba(52, 152, 219, 0.1)' }, { border: '#2ecc71', bg: 'rgba(46, 204, 113, 0.1)' },\n"
|
||||
" { border: '#f39c12', bg: 'rgba(243, 156, 18, 0.1)' }, { border: '#e74c3c', bg: 'rgba(231, 76, 60, 0.1)' },\n"
|
||||
" { border: '#9b59b6', bg: 'rgba(155, 89, 182, 0.1)' }, { border: '#1abc9c', bg: 'rgba(26, 188, 156, 0.1)' }\n"
|
||||
" ];\n"
|
||||
" const color = colors[index % colors.length];\n"
|
||||
" window.vhostCharts[chartId] = new Chart(canvas.getContext('2d'), {\n"
|
||||
" type: 'line',\n"
|
||||
" data: { datasets: [{ label: 'Throughput KB/s', data: p.throughput_history || [], borderColor: color.border, backgroundColor: color.bg }] },\n"
|
||||
" options: createBaseChartOptions(60, formatSizeTick)\n"
|
||||
" });\n"
|
||||
" } else {\n"
|
||||
" window.vhostCharts[chartId].data.datasets[0].data = p.throughput_history || [];\n"
|
||||
" window.vhostCharts[chartId].update('none');\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
" } catch (e) {\n"
|
||||
" console.error('Failed to fetch stats:', e);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" updateStats(); setInterval(updateStats,1000);\n"
|
||||
"\n"
|
||||
" updateStats();\n"
|
||||
" setInterval(updateStats, 1000);\n"
|
||||
" </script>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
@@ -344,91 +497,6 @@ void dashboard_serve_stats_api(connection_t *conn, const char *request_data, siz
|
||||
cJSON_AddNumberToObject(current, "load_5m", load5);
|
||||
cJSON_AddNumberToObject(current, "load_15m", load15);
|
||||
|
||||
monitor_compute_health_score();
|
||||
double current_rps = monitor_get_current_rps();
|
||||
cJSON_AddNumberToObject(current, "health_score", monitor.health_score);
|
||||
cJSON_AddNumberToObject(current, "requests_per_second", current_rps);
|
||||
cJSON_AddNumberToObject(current, "error_rate_1m", monitor.error_rate_1m);
|
||||
cJSON_AddNumberToObject(current, "uptime_seconds", (double)(time(NULL) - monitor.uptime_start));
|
||||
cJSON_AddNumberToObject(current, "peak_rps", monitor.peak_rps);
|
||||
cJSON_AddNumberToObject(current, "total_connections", (double)monitor.total_connections_accepted);
|
||||
|
||||
cJSON *latency = cJSON_CreateObject();
|
||||
if (latency) {
|
||||
cJSON_AddNumberToObject(latency, "p50_ms", histogram_percentile(&monitor.global_latency, 0.50));
|
||||
cJSON_AddNumberToObject(latency, "p90_ms", histogram_percentile(&monitor.global_latency, 0.90));
|
||||
cJSON_AddNumberToObject(latency, "p95_ms", histogram_percentile(&monitor.global_latency, 0.95));
|
||||
cJSON_AddNumberToObject(latency, "p99_ms", histogram_percentile(&monitor.global_latency, 0.99));
|
||||
cJSON_AddNumberToObject(latency, "mean_ms", histogram_mean(&monitor.global_latency));
|
||||
cJSON *histogram_arr = cJSON_CreateArray();
|
||||
if (histogram_arr) {
|
||||
for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
|
||||
cJSON_AddItemToArray(histogram_arr, cJSON_CreateNumber(monitor.global_latency.buckets[i]));
|
||||
}
|
||||
cJSON_AddItemToObject(latency, "histogram", histogram_arr);
|
||||
}
|
||||
cJSON *labels = cJSON_CreateArray();
|
||||
if (labels) {
|
||||
for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
|
||||
cJSON_AddItemToArray(labels, cJSON_CreateString(LATENCY_BUCKET_LABELS[i]));
|
||||
}
|
||||
cJSON_AddItemToObject(latency, "bucket_labels", labels);
|
||||
}
|
||||
cJSON_AddItemToObject(root, "latency", latency);
|
||||
}
|
||||
|
||||
uint64_t total_2xx = 0, total_3xx = 0, total_4xx = 0, total_5xx = 0;
|
||||
uint64_t methods[HTTP_METHOD_COUNT] = {0};
|
||||
uint64_t total_splice = 0, total_buffer = 0;
|
||||
uint64_t total_splice_bytes = 0, total_buffer_bytes = 0;
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s; s = s->next) {
|
||||
total_2xx += s->status_counts.status_2xx;
|
||||
total_3xx += s->status_counts.status_3xx;
|
||||
total_4xx += s->status_counts.status_4xx;
|
||||
total_5xx += s->status_counts.status_5xx;
|
||||
for (int i = 0; i < HTTP_METHOD_COUNT; i++) {
|
||||
methods[i] += s->method_counts.counts[i];
|
||||
}
|
||||
total_splice += s->splice_transfers;
|
||||
total_buffer += s->buffered_transfers;
|
||||
total_splice_bytes += s->bytes_via_splice;
|
||||
total_buffer_bytes += s->bytes_via_buffer;
|
||||
}
|
||||
|
||||
cJSON *status_codes = cJSON_CreateObject();
|
||||
if (status_codes) {
|
||||
cJSON_AddNumberToObject(status_codes, "2xx", (double)total_2xx);
|
||||
cJSON_AddNumberToObject(status_codes, "3xx", (double)total_3xx);
|
||||
cJSON_AddNumberToObject(status_codes, "4xx", (double)total_4xx);
|
||||
cJSON_AddNumberToObject(status_codes, "5xx", (double)total_5xx);
|
||||
cJSON_AddItemToObject(root, "status_codes", status_codes);
|
||||
}
|
||||
|
||||
cJSON *methods_obj = cJSON_CreateObject();
|
||||
if (methods_obj) {
|
||||
cJSON_AddNumberToObject(methods_obj, "GET", (double)methods[HTTP_METHOD_GET]);
|
||||
cJSON_AddNumberToObject(methods_obj, "POST", (double)methods[HTTP_METHOD_POST]);
|
||||
cJSON_AddNumberToObject(methods_obj, "PUT", (double)methods[HTTP_METHOD_PUT]);
|
||||
cJSON_AddNumberToObject(methods_obj, "DELETE", (double)methods[HTTP_METHOD_DELETE]);
|
||||
cJSON_AddNumberToObject(methods_obj, "PATCH", (double)methods[HTTP_METHOD_PATCH]);
|
||||
cJSON_AddNumberToObject(methods_obj, "HEAD", (double)methods[HTTP_METHOD_HEAD]);
|
||||
cJSON_AddNumberToObject(methods_obj, "OPTIONS", (double)methods[HTTP_METHOD_OPTIONS]);
|
||||
cJSON_AddNumberToObject(methods_obj, "OTHER", (double)methods[HTTP_METHOD_OTHER]);
|
||||
cJSON_AddItemToObject(root, "methods", methods_obj);
|
||||
}
|
||||
|
||||
cJSON *efficiency = cJSON_CreateObject();
|
||||
if (efficiency) {
|
||||
double total_transfers = (double)(total_splice + total_buffer);
|
||||
double splice_ratio = total_transfers > 0 ? (double)total_splice / total_transfers : 0;
|
||||
cJSON_AddNumberToObject(efficiency, "zero_copy_ratio", splice_ratio);
|
||||
cJSON_AddNumberToObject(efficiency, "splice_transfers", (double)total_splice);
|
||||
cJSON_AddNumberToObject(efficiency, "buffer_transfers", (double)total_buffer);
|
||||
cJSON_AddNumberToObject(efficiency, "bytes_via_splice", (double)total_splice_bytes);
|
||||
cJSON_AddNumberToObject(efficiency, "bytes_via_buffer", (double)total_buffer_bytes);
|
||||
cJSON_AddItemToObject(root, "efficiency", efficiency);
|
||||
}
|
||||
|
||||
cJSON_AddItemToObject(root, "cpu_history", format_history(&monitor.cpu_history, HISTORY_SECONDS));
|
||||
cJSON_AddItemToObject(root, "memory_history", format_history(&monitor.memory_history, HISTORY_SECONDS));
|
||||
cJSON_AddItemToObject(root, "network_rx_history", format_network_history(&monitor.network_history, HISTORY_SECONDS, "rx_kbps"));
|
||||
@@ -454,29 +522,6 @@ void dashboard_serve_stats_api(connection_t *conn, const char *request_data, siz
|
||||
cJSON_AddNumberToObject(p, "bytes_sent", s->bytes_sent);
|
||||
cJSON_AddNumberToObject(p, "bytes_recv", s->bytes_recv);
|
||||
cJSON_AddItemToObject(p, "throughput_history", format_history(&s->throughput_history, 60));
|
||||
|
||||
cJSON_AddNumberToObject(p, "latency_p50", histogram_percentile(&s->latency_histogram, 0.50));
|
||||
cJSON_AddNumberToObject(p, "latency_p90", histogram_percentile(&s->latency_histogram, 0.90));
|
||||
cJSON_AddNumberToObject(p, "latency_p95", histogram_percentile(&s->latency_histogram, 0.95));
|
||||
cJSON_AddNumberToObject(p, "latency_p99", histogram_percentile(&s->latency_histogram, 0.99));
|
||||
cJSON_AddNumberToObject(p, "rps", rate_tracker_get_rps(&s->requests_per_second));
|
||||
|
||||
double vhost_total = (double)(s->status_counts.status_2xx + s->status_counts.status_3xx +
|
||||
s->status_counts.status_4xx + s->status_counts.status_5xx);
|
||||
double error_rate = vhost_total > 0 ? (double)s->status_counts.status_5xx / vhost_total : 0;
|
||||
cJSON_AddNumberToObject(p, "error_rate", error_rate);
|
||||
|
||||
cJSON_AddNumberToObject(p, "status_2xx", (double)s->status_counts.status_2xx);
|
||||
cJSON_AddNumberToObject(p, "status_3xx", (double)s->status_counts.status_3xx);
|
||||
cJSON_AddNumberToObject(p, "status_4xx", (double)s->status_counts.status_4xx);
|
||||
cJSON_AddNumberToObject(p, "status_5xx", (double)s->status_counts.status_5xx);
|
||||
|
||||
cJSON_AddNumberToObject(p, "upstream_success", (double)s->upstream_connect_success);
|
||||
cJSON_AddNumberToObject(p, "upstream_failures", (double)s->upstream_connect_failures);
|
||||
cJSON_AddNumberToObject(p, "dns_failures", (double)s->dns_failures);
|
||||
cJSON_AddNumberToObject(p, "ssl_failures", (double)s->ssl_failures);
|
||||
cJSON_AddNumberToObject(p, "timeout_errors", (double)s->timeout_errors);
|
||||
|
||||
cJSON_AddItemToArray(processes, p);
|
||||
}
|
||||
}
|
||||
|
||||
Executable → Regular
-108
@@ -1,108 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "deque.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
void history_deque_init(history_deque_t *dq, int capacity) {
|
||||
if (!dq) return;
|
||||
dq->points = calloc((size_t)capacity, sizeof(history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void history_deque_push(history_deque_t *dq, double time, double value) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (history_point_t){ .time = time, .value = value };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void history_deque_free(history_deque_t *dq) {
|
||||
if (!dq) return;
|
||||
if (dq->points) {
|
||||
free(dq->points);
|
||||
dq->points = NULL;
|
||||
}
|
||||
dq->capacity = 0;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void network_history_deque_init(network_history_deque_t *dq, int capacity) {
|
||||
if (!dq) return;
|
||||
dq->points = calloc((size_t)capacity, sizeof(network_history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void network_history_deque_push(network_history_deque_t *dq, double time, double rx, double tx) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (network_history_point_t){ .time = time, .rx_kbps = rx, .tx_kbps = tx };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void network_history_deque_free(network_history_deque_t *dq) {
|
||||
if (!dq) return;
|
||||
if (dq->points) {
|
||||
free(dq->points);
|
||||
dq->points = NULL;
|
||||
}
|
||||
dq->capacity = 0;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void disk_history_deque_init(disk_history_deque_t *dq, int capacity) {
|
||||
if (!dq) return;
|
||||
dq->points = calloc((size_t)capacity, sizeof(disk_history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void disk_history_deque_push(disk_history_deque_t *dq, double time, double read_mbps, double write_mbps) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (disk_history_point_t){ .time = time, .read_mbps = read_mbps, .write_mbps = write_mbps };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void disk_history_deque_free(disk_history_deque_t *dq) {
|
||||
if (!dq) return;
|
||||
if (dq->points) {
|
||||
free(dq->points);
|
||||
dq->points = NULL;
|
||||
}
|
||||
dq->capacity = 0;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void request_time_deque_init(request_time_deque_t *dq, int capacity) {
|
||||
if (!dq) return;
|
||||
dq->times = calloc((size_t)capacity, sizeof(double));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void request_time_deque_push(request_time_deque_t *dq, double time_ms) {
|
||||
if (!dq || !dq->times) return;
|
||||
dq->times[dq->head] = time_ms;
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void request_time_deque_free(request_time_deque_t *dq) {
|
||||
if (!dq) return;
|
||||
if (dq->times) {
|
||||
free(dq->times);
|
||||
dq->times = NULL;
|
||||
}
|
||||
dq->capacity = 0;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_DEQUE_H
|
||||
#define RPROXY_DEQUE_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void history_deque_init(history_deque_t *dq, int capacity);
|
||||
void history_deque_push(history_deque_t *dq, double time, double value);
|
||||
void history_deque_free(history_deque_t *dq);
|
||||
|
||||
void network_history_deque_init(network_history_deque_t *dq, int capacity);
|
||||
void network_history_deque_push(network_history_deque_t *dq, double time, double rx, double tx);
|
||||
void network_history_deque_free(network_history_deque_t *dq);
|
||||
|
||||
void disk_history_deque_init(disk_history_deque_t *dq, int capacity);
|
||||
void disk_history_deque_push(disk_history_deque_t *dq, double time, double read_mbps, double write_mbps);
|
||||
void disk_history_deque_free(disk_history_deque_t *dq);
|
||||
|
||||
void request_time_deque_init(request_time_deque_t *dq, int capacity);
|
||||
void request_time_deque_push(request_time_deque_t *dq, double time_ms);
|
||||
void request_time_deque_free(request_time_deque_t *dq);
|
||||
|
||||
#endif
|
||||
@@ -1,50 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "epoll_utils.h"
|
||||
#include "logging.h"
|
||||
#include "types.h"
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
int epoll_fd = -1;
|
||||
|
||||
extern connection_t connections[MAX_FDS];
|
||||
|
||||
int epoll_utils_create(void) {
|
||||
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (epoll_fd == -1) {
|
||||
log_error("epoll_create1 failed");
|
||||
return -1;
|
||||
}
|
||||
return epoll_fd;
|
||||
}
|
||||
|
||||
void epoll_utils_add(int fd, uint32_t events) {
|
||||
struct epoll_event event = { .data.fd = fd, .events = events };
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
|
||||
log_error("epoll_ctl_add failed");
|
||||
close(fd);
|
||||
} else if (fd >= 0 && fd < MAX_FDS) {
|
||||
connections[fd].epoll_events = events;
|
||||
}
|
||||
}
|
||||
|
||||
void epoll_utils_modify(int fd, uint32_t events) {
|
||||
if (fd >= 0 && fd < MAX_FDS && connections[fd].epoll_events == events) {
|
||||
return;
|
||||
}
|
||||
struct epoll_event event = { .data.fd = fd, .events = events };
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) == -1) {
|
||||
if (errno != EBADF && errno != ENOENT) {
|
||||
log_debug("epoll_ctl_mod failed for fd %d: %s", fd, strerror(errno));
|
||||
}
|
||||
} else if (fd >= 0 && fd < MAX_FDS) {
|
||||
connections[fd].epoll_events = events;
|
||||
}
|
||||
}
|
||||
|
||||
void epoll_utils_remove(int fd) {
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_EPOLL_UTILS_H
|
||||
#define RPROXY_EPOLL_UTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern int epoll_fd;
|
||||
|
||||
int epoll_utils_create(void);
|
||||
void epoll_utils_add(int fd, uint32_t events);
|
||||
void epoll_utils_modify(int fd, uint32_t events);
|
||||
void epoll_utils_remove(int fd);
|
||||
|
||||
#endif
|
||||
@@ -1,167 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "forwarding.h"
|
||||
#include "buffer.h"
|
||||
#include "http.h"
|
||||
#include "monitor.h"
|
||||
#include "patch.h"
|
||||
#include "ssl_handler.h"
|
||||
#include "logging.h"
|
||||
#include "epoll_utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
extern connection_t connections[MAX_FDS];
|
||||
extern time_t cached_time;
|
||||
|
||||
static ssize_t forwarding_write(connection_t *dst, const char *data, size_t len) {
|
||||
if (dst->ssl && dst->ssl_handshake_done) {
|
||||
return ssl_write(dst, data, len);
|
||||
} else if (!dst->ssl) {
|
||||
return write(dst->fd, data, len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t forwarding_write_all(connection_t *dst, const char *data, size_t len) {
|
||||
size_t total_written = 0;
|
||||
|
||||
while (total_written < len) {
|
||||
ssize_t n = forwarding_write(dst, data + total_written, len - total_written);
|
||||
if (n > 0) {
|
||||
total_written += (size_t)n;
|
||||
} else if (n == 0) {
|
||||
break;
|
||||
} else {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return (ssize_t)total_written;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
int forwarding_try_splice(connection_t *src, connection_t *dst) {
|
||||
if (src->splice_pipe[0] < 0 || src->splice_pipe[1] < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t bytes_to_pipe = splice(src->fd, NULL, src->splice_pipe[1], NULL,
|
||||
CHUNK_SIZE, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
|
||||
|
||||
if (bytes_to_pipe <= 0) {
|
||||
if (bytes_to_pipe == 0) return 0;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) return -1;
|
||||
return -2;
|
||||
}
|
||||
|
||||
ssize_t bytes_from_pipe = splice(src->splice_pipe[0], NULL, dst->fd, NULL,
|
||||
(size_t)bytes_to_pipe, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
|
||||
|
||||
if (bytes_from_pipe < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
char discard[CHUNK_SIZE];
|
||||
if (read(src->splice_pipe[0], discard, (size_t)bytes_to_pipe) < 0) {
|
||||
// Ignore
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
src->last_activity = cached_time;
|
||||
dst->last_activity = cached_time;
|
||||
|
||||
if (src->vhost_stats) {
|
||||
monitor_record_bytes(src->vhost_stats, bytes_from_pipe, 0);
|
||||
monitor_record_splice_transfer(src->vhost_stats, (size_t)bytes_from_pipe);
|
||||
}
|
||||
|
||||
return (int)bytes_from_pipe;
|
||||
}
|
||||
#else
|
||||
int forwarding_try_splice(connection_t *src, connection_t *dst) {
|
||||
(void)src;
|
||||
(void)dst;
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void forwarding_handle(connection_t *conn, connection_t *pair, int direction) {
|
||||
if (!conn || !pair) return;
|
||||
|
||||
#ifdef __linux__
|
||||
int is_response = (direction == 1);
|
||||
if (conn->can_splice &&
|
||||
buffer_available_read(&conn->read_buf) == 0 &&
|
||||
buffer_available_read(&pair->write_buf) == 0 &&
|
||||
(!is_response || conn->response_headers_parsed)) {
|
||||
|
||||
int splice_result = forwarding_try_splice(conn, pair);
|
||||
if (splice_result > 0) return;
|
||||
if (splice_result == 0) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
return;
|
||||
}
|
||||
if (splice_result == -2) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
pair->state = CLIENT_STATE_CLOSING;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t available = conn->read_buf.tail - conn->read_buf.head;
|
||||
if (available == 0) return;
|
||||
|
||||
char *data = conn->read_buf.data + conn->read_buf.head;
|
||||
|
||||
if (direction == 1 && conn->type == CONN_TYPE_UPSTREAM && !conn->response_headers_parsed) {
|
||||
size_t headers_end = 0;
|
||||
if (http_find_headers_end(data, available, &headers_end)) {
|
||||
conn->response_headers_parsed = 1;
|
||||
if (conn->vhost_stats) {
|
||||
int status = http_extract_status_code(data, available);
|
||||
if (status > 0) {
|
||||
monitor_record_status(conn->vhost_stats, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t written = forwarding_write_all(pair, data, available);
|
||||
|
||||
log_debug("Forward dir=%d src=%d dst=%d avail=%zu written=%zd",
|
||||
direction, conn->fd, pair->fd, available, written);
|
||||
|
||||
if (written > 0) {
|
||||
conn->read_buf.head += (size_t)written;
|
||||
if (conn->vhost_stats) {
|
||||
monitor_record_bytes(conn->vhost_stats, written, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (conn->read_buf.head >= conn->read_buf.tail) {
|
||||
conn->read_buf.head = 0;
|
||||
conn->read_buf.tail = 0;
|
||||
} else if (written >= 0) {
|
||||
epoll_utils_modify(pair->fd, EPOLLIN | EPOLLOUT);
|
||||
}
|
||||
|
||||
if (written < 0) {
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
pair->state = CLIENT_STATE_CLOSING;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_FORWARDING_H
|
||||
#define RPROXY_FORWARDING_H
|
||||
|
||||
#include "types.h"
|
||||
#include <stddef.h>
|
||||
|
||||
void forwarding_handle(connection_t *conn, connection_t *pair, int direction);
|
||||
void forwarding_check_pipelining(connection_t *conn);
|
||||
int forwarding_try_splice(connection_t *src, connection_t *dst);
|
||||
|
||||
#endif
|
||||
Executable → Regular
Executable → Regular
@@ -1,69 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "histogram.h"
|
||||
#include <string.h>
|
||||
|
||||
const double LATENCY_BUCKET_BOUNDS[HISTOGRAM_BUCKETS] = {
|
||||
1.0, 2.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0,
|
||||
500.0, 1000.0, 2500.0, 5000.0, 10000.0, 30000.0, 60000.0, 1e9
|
||||
};
|
||||
|
||||
const char *LATENCY_BUCKET_LABELS[HISTOGRAM_BUCKETS] = {
|
||||
"0-1ms", "1-2ms", "2-5ms", "5-10ms", "10-25ms", "25-50ms", "50-100ms", "100-250ms",
|
||||
"250-500ms", "500ms-1s", "1-2.5s", "2.5-5s", "5-10s", "10-30s", "30-60s", "60s+"
|
||||
};
|
||||
|
||||
const double SIZE_BUCKET_BOUNDS[HISTOGRAM_BUCKETS] = {
|
||||
128.0, 512.0, 1024.0, 4096.0, 16384.0, 65536.0, 262144.0, 1048576.0,
|
||||
4194304.0, 16777216.0, 67108864.0, 268435456.0, 1073741824.0, 4294967296.0, 1e15, 1e18
|
||||
};
|
||||
|
||||
void histogram_init(histogram_t *h) {
|
||||
if (!h) return;
|
||||
memset(h, 0, sizeof(histogram_t));
|
||||
h->min_value = 1e18;
|
||||
h->max_value = -1e18;
|
||||
}
|
||||
|
||||
static void histogram_add_internal(histogram_t *h, double value, const double *bounds) {
|
||||
if (!h) return;
|
||||
h->total_count++;
|
||||
h->sum += value;
|
||||
if (value < h->min_value) h->min_value = value;
|
||||
if (value > h->max_value) h->max_value = value;
|
||||
|
||||
for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
|
||||
if (value <= bounds[i]) {
|
||||
h->buckets[i]++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
h->overflow++;
|
||||
}
|
||||
|
||||
void histogram_add(histogram_t *h, double value) {
|
||||
histogram_add_internal(h, value, LATENCY_BUCKET_BOUNDS);
|
||||
}
|
||||
|
||||
void histogram_add_with_bounds(histogram_t *h, double value, const double *bounds) {
|
||||
histogram_add_internal(h, value, bounds);
|
||||
}
|
||||
|
||||
double histogram_percentile(histogram_t *h, double p) {
|
||||
if (!h || h->total_count == 0) return 0.0;
|
||||
uint64_t target = (uint64_t)(h->total_count * p);
|
||||
uint64_t cumulative = 0;
|
||||
|
||||
for (int i = 0; i < HISTOGRAM_BUCKETS; i++) {
|
||||
cumulative += h->buckets[i];
|
||||
if (cumulative >= target) {
|
||||
return LATENCY_BUCKET_BOUNDS[i];
|
||||
}
|
||||
}
|
||||
return LATENCY_BUCKET_BOUNDS[HISTOGRAM_BUCKETS - 1];
|
||||
}
|
||||
|
||||
double histogram_mean(histogram_t *h) {
|
||||
if (!h || h->total_count == 0) return 0.0;
|
||||
return h->sum / h->total_count;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_HISTOGRAM_H
|
||||
#define RPROXY_HISTOGRAM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
extern const double LATENCY_BUCKET_BOUNDS[HISTOGRAM_BUCKETS];
|
||||
extern const char *LATENCY_BUCKET_LABELS[HISTOGRAM_BUCKETS];
|
||||
extern const double SIZE_BUCKET_BOUNDS[HISTOGRAM_BUCKETS];
|
||||
|
||||
void histogram_init(histogram_t *h);
|
||||
void histogram_add(histogram_t *h, double value);
|
||||
void histogram_add_with_bounds(histogram_t *h, double value, const double *bounds);
|
||||
double histogram_percentile(histogram_t *h, double p);
|
||||
double histogram_mean(histogram_t *h);
|
||||
|
||||
#endif
|
||||
Executable → Regular
-93
@@ -283,96 +283,3 @@ int http_find_header_line_bounds(const char* data, size_t len, const char* name,
|
||||
*line_end = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_extract_status_code(const char *data, size_t len) {
|
||||
if (!data || len < 12) return 0;
|
||||
|
||||
if (strncmp(data, "HTTP/1.", 7) != 0 && strncmp(data, "HTTP/2", 6) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *p = data;
|
||||
while (p < data + len && *p != ' ') p++;
|
||||
if (p >= data + len) return 0;
|
||||
while (p < data + len && *p == ' ') p++;
|
||||
if (p >= data + len) return 0;
|
||||
|
||||
int status = 0;
|
||||
for (int i = 0; i < 3 && p + i < data + len; i++) {
|
||||
if (p[i] >= '0' && p[i] <= '9') {
|
||||
status = status * 10 + (p[i] - '0');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (status >= 100 && status < 600) ? status : 0;
|
||||
}
|
||||
|
||||
void http_normalize_uri_path(const char *uri, char *normalized, size_t normalized_size) {
|
||||
if (!uri || !normalized || normalized_size == 0) return;
|
||||
|
||||
size_t uri_len = strlen(uri);
|
||||
size_t out_pos = 0;
|
||||
size_t i = 0;
|
||||
|
||||
const char *query = strchr(uri, '?');
|
||||
size_t path_len = query ? (size_t)(query - uri) : uri_len;
|
||||
|
||||
while (i < path_len && out_pos < normalized_size - 1) {
|
||||
if (uri[i] == '/') {
|
||||
if (out_pos == 0 || normalized[out_pos - 1] != '/') {
|
||||
normalized[out_pos++] = '/';
|
||||
}
|
||||
i++;
|
||||
|
||||
if (i < path_len && uri[i] == '.') {
|
||||
if (i + 1 >= path_len || uri[i + 1] == '/' || uri[i + 1] == '?') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (uri[i + 1] == '.' && (i + 2 >= path_len || uri[i + 2] == '/' || uri[i + 2] == '?')) {
|
||||
if (out_pos > 1) {
|
||||
out_pos--;
|
||||
while (out_pos > 0 && normalized[out_pos - 1] != '/') {
|
||||
out_pos--;
|
||||
}
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
normalized[out_pos++] = uri[i++];
|
||||
}
|
||||
}
|
||||
|
||||
if (query && out_pos < normalized_size - 1) {
|
||||
size_t query_len = uri_len - (query - uri);
|
||||
if (out_pos + query_len >= normalized_size) {
|
||||
query_len = normalized_size - out_pos - 1;
|
||||
}
|
||||
memcpy(normalized + out_pos, query, query_len);
|
||||
out_pos += query_len;
|
||||
}
|
||||
|
||||
normalized[out_pos] = '\0';
|
||||
|
||||
if (out_pos == 0 && normalized_size > 1) {
|
||||
normalized[0] = '/';
|
||||
normalized[1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
int http_uri_is_internal_route(const char *uri) {
|
||||
if (!uri) return 0;
|
||||
|
||||
char normalized[2048];
|
||||
http_normalize_uri_path(uri, normalized, sizeof(normalized));
|
||||
|
||||
if (strncmp(normalized, "/rproxy/", 8) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Executable → Regular
-3
@@ -12,8 +12,5 @@ long http_get_content_length(const char *headers, size_t headers_len);
|
||||
int http_find_headers_end(const char *data, size_t len, size_t *headers_end);
|
||||
int http_rewrite_content_length(char *headers, size_t *headers_len, size_t max_len, long new_length);
|
||||
int http_find_header_line_bounds(const char* data, size_t len, const char* name, const char** line_start, const char** line_end);
|
||||
int http_extract_status_code(const char *data, size_t len);
|
||||
int http_uri_is_internal_route(const char *uri);
|
||||
void http_normalize_uri_path(const char *uri, char *normalized, size_t normalized_size);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "http_response.h"
|
||||
#include "time_utils.h"
|
||||
#include "epoll_utils.h"
|
||||
#include "buffer.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
int http_response_build(const http_response_params_t *params, char *buf, size_t buf_size) {
|
||||
if (!params || !buf || buf_size == 0) return -1;
|
||||
|
||||
char date_buf[64];
|
||||
time_format_http_date_now(date_buf, sizeof(date_buf));
|
||||
|
||||
const char *content_type = params->content_type ? params->content_type : "text/plain; charset=utf-8";
|
||||
const char *body = params->body ? params->body : "";
|
||||
size_t body_len = params->body_len > 0 ? params->body_len : strlen(body);
|
||||
const char *connection = params->keep_alive ? "keep-alive" : "close";
|
||||
const char *extra = params->extra_headers ? params->extra_headers : "";
|
||||
|
||||
int len = snprintf(buf, buf_size,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %zu\r\n"
|
||||
"Connection: %s\r\n"
|
||||
"Date: %s\r\n"
|
||||
"Server: ReverseProxy/4.0\r\n"
|
||||
"%s"
|
||||
"\r\n",
|
||||
params->code, params->status,
|
||||
content_type, body_len, connection, date_buf, extra);
|
||||
|
||||
if (len < 0 || (size_t)len >= buf_size) return -1;
|
||||
|
||||
if (body_len > 0 && (size_t)len + body_len < buf_size) {
|
||||
memcpy(buf + len, body, body_len);
|
||||
len += (int)body_len;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void http_response_send(connection_t *conn, const http_response_params_t *params) {
|
||||
if (!conn || !params) return;
|
||||
|
||||
char response[ERROR_RESPONSE_SIZE];
|
||||
int len = http_response_build(params, response, sizeof(response));
|
||||
if (len <= 0) return;
|
||||
|
||||
if (buffer_ensure_capacity(&conn->write_buf, conn->write_buf.tail + (size_t)len) == 0) {
|
||||
memcpy(conn->write_buf.data + conn->write_buf.tail, response, (size_t)len);
|
||||
conn->write_buf.tail += (size_t)len;
|
||||
|
||||
struct epoll_event event = { .data.fd = conn->fd, .events = EPOLLIN | EPOLLOUT };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn->fd, &event);
|
||||
}
|
||||
}
|
||||
|
||||
void http_response_send_error(connection_t *conn, int code, const char *status, const char *body) {
|
||||
if (!conn || !status || !body) return;
|
||||
|
||||
http_response_params_t params = {
|
||||
.code = code,
|
||||
.status = status,
|
||||
.body = body,
|
||||
.keep_alive = 0
|
||||
};
|
||||
|
||||
http_response_send(conn, ¶ms);
|
||||
conn->state = CLIENT_STATE_ERROR;
|
||||
conn->request.keep_alive = 0;
|
||||
}
|
||||
|
||||
void http_response_send_auth_required(connection_t *conn, const char *realm) {
|
||||
if (!conn) return;
|
||||
|
||||
char extra_headers[256];
|
||||
snprintf(extra_headers, sizeof(extra_headers),
|
||||
"WWW-Authenticate: Basic realm=\"%s\"\r\n",
|
||||
realm ? realm : "Protected Area");
|
||||
|
||||
http_response_params_t params = {
|
||||
.code = 401,
|
||||
.status = "Unauthorized",
|
||||
.body = "401 Unauthorized - Authentication required",
|
||||
.extra_headers = extra_headers,
|
||||
.keep_alive = 0
|
||||
};
|
||||
|
||||
http_response_send(conn, ¶ms);
|
||||
conn->state = CLIENT_STATE_ERROR;
|
||||
conn->request.keep_alive = 0;
|
||||
}
|
||||
|
||||
void http_response_send_pipeline_rejected(connection_t *conn) {
|
||||
if (!conn) return;
|
||||
|
||||
http_response_params_t params = {
|
||||
.code = 400,
|
||||
.status = "Bad Request",
|
||||
.body = "400 Bad Request - Request pipelining is not supported",
|
||||
.keep_alive = 0
|
||||
};
|
||||
|
||||
char response[ERROR_RESPONSE_SIZE];
|
||||
int len = http_response_build(¶ms, response, sizeof(response));
|
||||
if (len <= 0) return;
|
||||
|
||||
if (buffer_ensure_capacity(&conn->write_buf, conn->write_buf.tail + (size_t)len) == 0) {
|
||||
memcpy(conn->write_buf.data + conn->write_buf.tail, response, (size_t)len);
|
||||
conn->write_buf.tail += (size_t)len;
|
||||
}
|
||||
|
||||
conn->state = CLIENT_STATE_CLOSING;
|
||||
conn->request.keep_alive = 0;
|
||||
|
||||
struct epoll_event event = { .data.fd = conn->fd, .events = EPOLLOUT };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, conn->fd, &event);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_HTTP_RESPONSE_H
|
||||
#define RPROXY_HTTP_RESPONSE_H
|
||||
|
||||
#include "types.h"
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
int code;
|
||||
const char *status;
|
||||
const char *content_type;
|
||||
const char *body;
|
||||
size_t body_len;
|
||||
const char *extra_headers;
|
||||
int keep_alive;
|
||||
} http_response_params_t;
|
||||
|
||||
int http_response_build(const http_response_params_t *params, char *buf, size_t buf_size);
|
||||
|
||||
void http_response_send_error(connection_t *conn, int code, const char *status, const char *body);
|
||||
void http_response_send_auth_required(connection_t *conn, const char *realm);
|
||||
void http_response_send_pipeline_rejected(connection_t *conn);
|
||||
void http_response_send(connection_t *conn, const http_response_params_t *params);
|
||||
|
||||
#endif
|
||||
Executable → Regular
+1
-1
@@ -32,7 +32,7 @@ static void rotate_log_file(void) {
|
||||
g_log_file = NULL;
|
||||
}
|
||||
|
||||
char old_path[536], new_path[536];
|
||||
char old_path[520], new_path[520];
|
||||
|
||||
snprintf(old_path, sizeof(old_path), "%s.%d", g_log_path, LOG_MAX_ROTATIONS);
|
||||
unlink(old_path);
|
||||
|
||||
Executable → Regular
Executable → Regular
+4
-13
@@ -1,5 +1,3 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
@@ -9,7 +7,6 @@
|
||||
#include <errno.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "logging.h"
|
||||
@@ -17,15 +14,10 @@
|
||||
#include "monitor.h"
|
||||
#include "ssl_handler.h"
|
||||
#include "connection.h"
|
||||
#include "epoll_utils.h"
|
||||
#include "rate_limit.h"
|
||||
#include "auth.h"
|
||||
#include "health_check.h"
|
||||
|
||||
#ifndef CONFIG_RELOAD_INTERVAL_SECONDS
|
||||
#define CONFIG_RELOAD_INTERVAL_SECONDS 5
|
||||
#endif
|
||||
|
||||
static volatile sig_atomic_t g_shutdown = 0;
|
||||
static volatile sig_atomic_t g_reload_config = 0;
|
||||
static const char *g_config_file = NULL;
|
||||
@@ -170,8 +162,9 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
health_check_init();
|
||||
|
||||
if (epoll_utils_create() == -1) {
|
||||
log_error("epoll_create failed");
|
||||
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (epoll_fd == -1) {
|
||||
log_error("epoll_create1 failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -203,13 +196,11 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
connection_update_cached_time();
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
connection_handle_event(&events[i]);
|
||||
}
|
||||
|
||||
time_t current_time = cached_time;
|
||||
time_t current_time = time(NULL);
|
||||
|
||||
if (current_time > last_monitor_update) {
|
||||
monitor_update();
|
||||
|
||||
+457
-326
@@ -1,31 +1,156 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "monitor.h"
|
||||
#include "histogram.h"
|
||||
#include "deque.h"
|
||||
#include "rate_tracker.h"
|
||||
#include "stats_collector.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <math.h>
|
||||
|
||||
system_monitor_t monitor = {0};
|
||||
system_monitor_t monitor;
|
||||
|
||||
static double get_current_time_seconds(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
||||
void history_deque_init(history_deque_t *dq, int capacity) {
|
||||
dq->points = calloc(capacity, sizeof(history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void history_deque_push(history_deque_t *dq, double time, double value) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (history_point_t){ .time = time, .value = value };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void network_history_deque_init(network_history_deque_t *dq, int capacity) {
|
||||
dq->points = calloc(capacity, sizeof(network_history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void network_history_deque_push(network_history_deque_t *dq, double time, double rx, double tx) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (network_history_point_t){ .time = time, .rx_kbps = rx, .tx_kbps = tx };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void disk_history_deque_init(disk_history_deque_t *dq, int capacity) {
|
||||
dq->points = calloc(capacity, sizeof(disk_history_point_t));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void disk_history_deque_push(disk_history_deque_t *dq, double time, double read_mbps, double write_mbps) {
|
||||
if (!dq || !dq->points) return;
|
||||
dq->points[dq->head] = (disk_history_point_t){ .time = time, .read_mbps = read_mbps, .write_mbps = write_mbps };
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
void request_time_deque_init(request_time_deque_t *dq, int capacity) {
|
||||
dq->times = calloc(capacity, sizeof(double));
|
||||
dq->capacity = capacity;
|
||||
dq->head = 0;
|
||||
dq->count = 0;
|
||||
}
|
||||
|
||||
void request_time_deque_push(request_time_deque_t *dq, double time_ms) {
|
||||
if (!dq || !dq->times) return;
|
||||
dq->times[dq->head] = time_ms;
|
||||
dq->head = (dq->head + 1) % dq->capacity;
|
||||
if (dq->count < dq->capacity) dq->count++;
|
||||
}
|
||||
|
||||
#define DATA_RETENTION_SECONDS (24 * 60 * 60)
|
||||
|
||||
static void init_db(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
char *err_msg = 0;
|
||||
|
||||
sqlite3_exec(monitor.db, "PRAGMA journal_mode=WAL;", 0, 0, NULL);
|
||||
sqlite3_exec(monitor.db, "PRAGMA synchronous=NORMAL;", 0, 0, NULL);
|
||||
|
||||
const char *sql_create_stats =
|
||||
"CREATE TABLE IF NOT EXISTS vhost_stats ("
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" vhost TEXT NOT NULL,"
|
||||
" timestamp REAL NOT NULL,"
|
||||
" http_requests INTEGER DEFAULT 0,"
|
||||
" websocket_requests INTEGER DEFAULT 0,"
|
||||
" total_requests INTEGER DEFAULT 0,"
|
||||
" bytes_sent INTEGER DEFAULT 0,"
|
||||
" bytes_recv INTEGER DEFAULT 0,"
|
||||
" avg_request_time_ms REAL DEFAULT 0,"
|
||||
" UNIQUE(vhost, timestamp)"
|
||||
");";
|
||||
|
||||
const char *sql_create_totals =
|
||||
"CREATE TABLE IF NOT EXISTS vhost_totals ("
|
||||
" vhost TEXT PRIMARY KEY,"
|
||||
" http_requests INTEGER DEFAULT 0,"
|
||||
" websocket_requests INTEGER DEFAULT 0,"
|
||||
" total_requests INTEGER DEFAULT 0,"
|
||||
" bytes_sent INTEGER DEFAULT 0,"
|
||||
" bytes_recv INTEGER DEFAULT 0"
|
||||
");";
|
||||
|
||||
const char *sql_idx_vhost_ts = "CREATE INDEX IF NOT EXISTS idx_vhost_timestamp ON vhost_stats(vhost, timestamp);";
|
||||
const char *sql_idx_ts = "CREATE INDEX IF NOT EXISTS idx_timestamp ON vhost_stats(timestamp);";
|
||||
|
||||
if (sqlite3_exec(monitor.db, sql_create_stats, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
if (sqlite3_exec(monitor.db, sql_create_totals, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
if (sqlite3_exec(monitor.db, sql_idx_vhost_ts, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
if (sqlite3_exec(monitor.db, sql_idx_ts, 0, 0, &err_msg) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n", err_msg);
|
||||
sqlite3_free(err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void load_stats_from_db(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
sqlite3_stmt *res;
|
||||
const char *sql =
|
||||
"SELECT vhost, http_requests, websocket_requests, total_requests, "
|
||||
"bytes_sent, bytes_recv FROM vhost_totals";
|
||||
|
||||
if (sqlite3_prepare_v2(monitor.db, sql, -1, &res, 0) != SQLITE_OK) {
|
||||
fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(monitor.db));
|
||||
return;
|
||||
}
|
||||
|
||||
int vhost_count = 0;
|
||||
while (sqlite3_step(res) == SQLITE_ROW) {
|
||||
vhost_stats_t *stats = monitor_get_or_create_vhost_stats((const char*)sqlite3_column_text(res, 0));
|
||||
if (stats) {
|
||||
stats->http_requests = sqlite3_column_int64(res, 1);
|
||||
stats->websocket_requests = sqlite3_column_int64(res, 2);
|
||||
stats->total_requests = sqlite3_column_int64(res, 3);
|
||||
stats->bytes_sent = sqlite3_column_int64(res, 4);
|
||||
stats->bytes_recv = sqlite3_column_int64(res, 5);
|
||||
vhost_count++;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(res);
|
||||
log_info("Loaded statistics for %d vhosts from database", vhost_count);
|
||||
}
|
||||
|
||||
void monitor_init(const char *db_file) {
|
||||
memset(&monitor, 0, sizeof(monitor));
|
||||
memset(&monitor, 0, sizeof(system_monitor_t));
|
||||
monitor.start_time = time(NULL);
|
||||
monitor.uptime_start = monitor.start_time;
|
||||
monitor.health_score = 100.0;
|
||||
|
||||
history_deque_init(&monitor.cpu_history, HISTORY_SECONDS);
|
||||
history_deque_init(&monitor.memory_history, HISTORY_SECONDS);
|
||||
@@ -36,129 +161,355 @@ void monitor_init(const char *db_file) {
|
||||
history_deque_init(&monitor.load5_history, HISTORY_SECONDS);
|
||||
history_deque_init(&monitor.load15_history, HISTORY_SECONDS);
|
||||
|
||||
histogram_init(&monitor.global_latency);
|
||||
histogram_init(&monitor.connection_lifetime);
|
||||
rate_tracker_init(&monitor.global_rps);
|
||||
|
||||
stats_get_network(&monitor.last_net_sent, &monitor.last_net_recv);
|
||||
stats_get_disk(&monitor.last_disk_read, &monitor.last_disk_write);
|
||||
monitor.last_net_update_time = get_current_time_seconds();
|
||||
monitor.last_disk_update_time = monitor.last_net_update_time;
|
||||
|
||||
if (db_file) {
|
||||
int rc = sqlite3_open(db_file, &monitor.db);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_debug("Cannot open stats database: %s", sqlite3_errmsg(monitor.db));
|
||||
if (sqlite3_open(db_file, &monitor.db) != SQLITE_OK) {
|
||||
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(monitor.db));
|
||||
if (monitor.db) {
|
||||
sqlite3_close(monitor.db);
|
||||
monitor.db = NULL;
|
||||
} else {
|
||||
char *err = NULL;
|
||||
const char *sql =
|
||||
"CREATE TABLE IF NOT EXISTS stats_history ("
|
||||
" timestamp INTEGER PRIMARY KEY,"
|
||||
" cpu_percent REAL,"
|
||||
" memory_gb REAL,"
|
||||
" active_connections INTEGER,"
|
||||
" requests_per_second REAL"
|
||||
");";
|
||||
sqlite3_exec(monitor.db, sql, NULL, NULL, &err);
|
||||
if (err) {
|
||||
sqlite3_free(err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
init_db();
|
||||
load_stats_from_db();
|
||||
}
|
||||
|
||||
log_info("Monitor initialized");
|
||||
monitor_update();
|
||||
}
|
||||
|
||||
void monitor_cleanup(void) {
|
||||
history_deque_free(&monitor.cpu_history);
|
||||
history_deque_free(&monitor.memory_history);
|
||||
network_history_deque_free(&monitor.network_history);
|
||||
disk_history_deque_free(&monitor.disk_history);
|
||||
history_deque_free(&monitor.throughput_history);
|
||||
history_deque_free(&monitor.load1_history);
|
||||
history_deque_free(&monitor.load5_history);
|
||||
history_deque_free(&monitor.load15_history);
|
||||
|
||||
vhost_stats_t *current = monitor.vhost_stats_head;
|
||||
while (current) {
|
||||
vhost_stats_t *next = current->next;
|
||||
history_deque_free(¤t->throughput_history);
|
||||
request_time_deque_free(¤t->request_times);
|
||||
free(current);
|
||||
current = next;
|
||||
}
|
||||
monitor.vhost_stats_head = NULL;
|
||||
|
||||
if (monitor.db) {
|
||||
sqlite3_close(monitor.db);
|
||||
monitor.db = NULL;
|
||||
}
|
||||
|
||||
log_info("Monitor cleanup complete");
|
||||
vhost_stats_t *current = monitor.vhost_stats_head;
|
||||
while (current) {
|
||||
vhost_stats_t *next = current->next;
|
||||
if (current->throughput_history.points) free(current->throughput_history.points);
|
||||
if (current->request_times.times) free(current->request_times.times);
|
||||
free(current);
|
||||
current = next;
|
||||
}
|
||||
monitor.vhost_stats_head = NULL;
|
||||
|
||||
if (monitor.cpu_history.points) { free(monitor.cpu_history.points); monitor.cpu_history.points = NULL; }
|
||||
if (monitor.memory_history.points) { free(monitor.memory_history.points); monitor.memory_history.points = NULL; }
|
||||
if (monitor.network_history.points) { free(monitor.network_history.points); monitor.network_history.points = NULL; }
|
||||
if (monitor.disk_history.points) { free(monitor.disk_history.points); monitor.disk_history.points = NULL; }
|
||||
if (monitor.throughput_history.points) { free(monitor.throughput_history.points); monitor.throughput_history.points = NULL; }
|
||||
if (monitor.load1_history.points) { free(monitor.load1_history.points); monitor.load1_history.points = NULL; }
|
||||
if (monitor.load5_history.points) { free(monitor.load5_history.points); monitor.load5_history.points = NULL; }
|
||||
if (monitor.load15_history.points) { free(monitor.load15_history.points); monitor.load15_history.points = NULL; }
|
||||
}
|
||||
|
||||
vhost_stats_t *monitor_get_or_create_vhost_stats(const char *vhost_name) {
|
||||
if (!vhost_name || vhost_name[0] == '\0') return NULL;
|
||||
static void cleanup_old_stats(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s; s = s->next) {
|
||||
if (strcmp(s->vhost_name, vhost_name) == 0) {
|
||||
return s;
|
||||
double cutoff = (double)time(NULL) - DATA_RETENTION_SECONDS;
|
||||
sqlite3_stmt *stmt;
|
||||
const char *sql = "DELETE FROM vhost_stats WHERE timestamp < ?;";
|
||||
|
||||
if (sqlite3_prepare_v2(monitor.db, sql, -1, &stmt, NULL) != SQLITE_OK) return;
|
||||
sqlite3_bind_double(stmt, 1, cutoff);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
static void save_stats_to_db(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
sqlite3_exec(monitor.db, "BEGIN TRANSACTION;", 0, 0, NULL);
|
||||
|
||||
sqlite3_stmt *stmt_totals;
|
||||
const char *sql_totals =
|
||||
"INSERT OR REPLACE INTO vhost_totals "
|
||||
"(vhost, http_requests, websocket_requests, total_requests, bytes_sent, bytes_recv) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?);";
|
||||
|
||||
sqlite3_stmt *stmt_stats;
|
||||
const char *sql_stats =
|
||||
"INSERT OR REPLACE INTO vhost_stats "
|
||||
"(vhost, timestamp, http_requests, websocket_requests, total_requests, "
|
||||
"bytes_sent, bytes_recv, avg_request_time_ms) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?);";
|
||||
|
||||
if (sqlite3_prepare_v2(monitor.db, sql_totals, -1, &stmt_totals, NULL) != SQLITE_OK) {
|
||||
sqlite3_exec(monitor.db, "ROLLBACK;", 0, 0, NULL);
|
||||
return;
|
||||
}
|
||||
if (sqlite3_prepare_v2(monitor.db, sql_stats, -1, &stmt_stats, NULL) != SQLITE_OK) {
|
||||
sqlite3_finalize(stmt_totals);
|
||||
sqlite3_exec(monitor.db, "ROLLBACK;", 0, 0, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
double current_time = (double)time(NULL);
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) {
|
||||
if (s->request_times.count > 0) {
|
||||
double total_time = 0;
|
||||
for(int i = 0; i < s->request_times.count; i++) {
|
||||
total_time += s->request_times.times[i];
|
||||
}
|
||||
s->avg_request_time_ms = total_time / s->request_times.count;
|
||||
}
|
||||
|
||||
sqlite3_bind_text(stmt_totals, 1, s->vhost_name, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(stmt_totals, 2, s->http_requests);
|
||||
sqlite3_bind_int64(stmt_totals, 3, s->websocket_requests);
|
||||
sqlite3_bind_int64(stmt_totals, 4, s->total_requests);
|
||||
sqlite3_bind_int64(stmt_totals, 5, s->bytes_sent);
|
||||
sqlite3_bind_int64(stmt_totals, 6, s->bytes_recv);
|
||||
sqlite3_step(stmt_totals);
|
||||
sqlite3_reset(stmt_totals);
|
||||
|
||||
sqlite3_bind_text(stmt_stats, 1, s->vhost_name, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_double(stmt_stats, 2, current_time);
|
||||
sqlite3_bind_int64(stmt_stats, 3, s->http_requests);
|
||||
sqlite3_bind_int64(stmt_stats, 4, s->websocket_requests);
|
||||
sqlite3_bind_int64(stmt_stats, 5, s->total_requests);
|
||||
sqlite3_bind_int64(stmt_stats, 6, s->bytes_sent);
|
||||
sqlite3_bind_int64(stmt_stats, 7, s->bytes_recv);
|
||||
sqlite3_bind_double(stmt_stats, 8, s->avg_request_time_ms);
|
||||
sqlite3_step(stmt_stats);
|
||||
sqlite3_reset(stmt_stats);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt_totals);
|
||||
sqlite3_finalize(stmt_stats);
|
||||
sqlite3_exec(monitor.db, "COMMIT;", 0, 0, NULL);
|
||||
|
||||
static time_t last_cleanup = 0;
|
||||
if (current_time - last_cleanup >= 3600) {
|
||||
cleanup_old_stats();
|
||||
last_cleanup = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
static double get_cpu_usage(void) {
|
||||
static long long prev_user = 0, prev_nice = 0, prev_system = 0, prev_idle = 0;
|
||||
long long user, nice, system, idle, iowait, irq, softirq;
|
||||
|
||||
FILE *f = fopen("/proc/stat", "r");
|
||||
if (!f) return 0.0;
|
||||
|
||||
if (fscanf(f, "cpu %lld %lld %lld %lld %lld %lld %lld",
|
||||
&user, &nice, &system, &idle, &iowait, &irq, &softirq) != 7) {
|
||||
fclose(f);
|
||||
return 0.0;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
long long prev_total = prev_user + prev_nice + prev_system + prev_idle;
|
||||
long long total = user + nice + system + idle;
|
||||
long long totald = total - prev_total;
|
||||
long long idled = idle - prev_idle;
|
||||
|
||||
prev_user = user; prev_nice = nice; prev_system = system; prev_idle = idle;
|
||||
return totald == 0 ? 0.0 : (double)(totald - idled) * 100.0 / totald;
|
||||
}
|
||||
|
||||
static void get_memory_usage(double *used_gb) {
|
||||
struct sysinfo info;
|
||||
if (sysinfo(&info) != 0) {
|
||||
*used_gb = 0;
|
||||
return;
|
||||
}
|
||||
*used_gb = (double)(info.totalram - info.freeram - info.bufferram) * info.mem_unit / (1024.0 * 1024.0 * 1024.0);
|
||||
}
|
||||
|
||||
static void get_network_stats(long long *bytes_sent, long long *bytes_recv) {
|
||||
FILE *f = fopen("/proc/net/dev", "r");
|
||||
if (!f) {
|
||||
*bytes_sent = 0;
|
||||
*bytes_recv = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
if (!fgets(line, sizeof(line), f) || !fgets(line, sizeof(line), f)) {
|
||||
fclose(f);
|
||||
*bytes_sent = 0;
|
||||
*bytes_recv = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
long long total_recv = 0, total_sent = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char iface[32];
|
||||
long long r, t;
|
||||
if (sscanf(line, "%31[^:]: %lld %*d %*d %*d %*d %*d %*d %*d %lld", iface, &r, &t) == 3) {
|
||||
char *trimmed = iface;
|
||||
while (*trimmed == ' ') trimmed++;
|
||||
if (strcmp(trimmed, "lo") != 0) {
|
||||
total_recv += r;
|
||||
total_sent += t;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
*bytes_sent = total_sent;
|
||||
*bytes_recv = total_recv;
|
||||
}
|
||||
|
||||
static void get_disk_stats(long long *sectors_read, long long *sectors_written) {
|
||||
FILE *f = fopen("/proc/diskstats", "r");
|
||||
if (!f) {
|
||||
*sectors_read = 0;
|
||||
*sectors_written = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char line[2048];
|
||||
long long total_read = 0, total_written = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char device[64];
|
||||
long long sectors_r = 0, sectors_w = 0;
|
||||
int nfields = 0;
|
||||
|
||||
char major[16], minor[16], dev[64];
|
||||
char rc[32], rm[32], sr[32], rtm[32], rtm2[32], wc[32], wm[32], sw[32];
|
||||
nfields = sscanf(line, "%15s %15s %63s %31s %31s %31s %31s %31s %31s %31s %31s %31s",
|
||||
major, minor, dev, rc, rm, sr, rtm, rtm2, wc, wm, sw, sw);
|
||||
if (nfields >= 11) {
|
||||
strncpy(device, dev, sizeof(device)-1);
|
||||
device[sizeof(device)-1] = '\0';
|
||||
char *endptr;
|
||||
sectors_r = strtoll(sr, &endptr, 10);
|
||||
if (endptr == sr) sectors_r = 0;
|
||||
sectors_w = strtoll(sw, &endptr, 10);
|
||||
if (endptr == sw) sectors_w = 0;
|
||||
|
||||
if (strncmp(device, "loop", 4) != 0 && strncmp(device, "ram", 3) != 0) {
|
||||
int len = strlen(device);
|
||||
if ((strncmp(device, "sd", 2) == 0 && len == 3) ||
|
||||
(strncmp(device, "nvme", 4) == 0 && strstr(device, "n1p") == NULL) ||
|
||||
(strncmp(device, "vd", 2) == 0 && len == 3) ||
|
||||
(strncmp(device, "hd", 2) == 0 && len == 3)) {
|
||||
total_read += sectors_r;
|
||||
total_written += sectors_w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
*sectors_read = total_read;
|
||||
*sectors_written = total_written;
|
||||
}
|
||||
|
||||
static void get_load_averages(double *load1, double *load5, double *load15) {
|
||||
FILE *f = fopen("/proc/loadavg", "r");
|
||||
if (!f) {
|
||||
*load1 = *load5 = *load15 = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fscanf(f, "%lf %lf %lf", load1, load5, load15) != 3) {
|
||||
*load1 = *load5 = *load15 = 0.0;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void monitor_update(void) {
|
||||
double current_time = time(NULL);
|
||||
|
||||
history_deque_push(&monitor.cpu_history, current_time, get_cpu_usage());
|
||||
|
||||
double mem_used_gb;
|
||||
get_memory_usage(&mem_used_gb);
|
||||
history_deque_push(&monitor.memory_history, current_time, mem_used_gb);
|
||||
|
||||
long long net_sent, net_recv;
|
||||
get_network_stats(&net_sent, &net_recv);
|
||||
double time_delta = current_time - monitor.last_net_update_time;
|
||||
if (time_delta > 0 && monitor.last_net_update_time > 0) {
|
||||
double rx = (net_recv - monitor.last_net_recv) / time_delta / 1024.0;
|
||||
double tx = (net_sent - monitor.last_net_sent) / time_delta / 1024.0;
|
||||
network_history_deque_push(&monitor.network_history, current_time, fmax(0, rx), fmax(0, tx));
|
||||
history_deque_push(&monitor.throughput_history, current_time, fmax(0, rx + tx));
|
||||
}
|
||||
monitor.last_net_sent = net_sent;
|
||||
monitor.last_net_recv = net_recv;
|
||||
monitor.last_net_update_time = current_time;
|
||||
|
||||
long long disk_read, disk_write;
|
||||
get_disk_stats(&disk_read, &disk_write);
|
||||
double disk_time_delta = current_time - monitor.last_disk_update_time;
|
||||
if (disk_time_delta > 0 && monitor.last_disk_update_time > 0) {
|
||||
double read_mbps = (disk_read - monitor.last_disk_read) * 512.0 / disk_time_delta / (1024.0 * 1024.0);
|
||||
double write_mbps = (disk_write - monitor.last_disk_write) * 512.0 / disk_time_delta / (1024.0 * 1024.0);
|
||||
disk_history_deque_push(&monitor.disk_history, current_time, fmax(0, read_mbps), fmax(0, write_mbps));
|
||||
}
|
||||
monitor.last_disk_read = disk_read;
|
||||
monitor.last_disk_write = disk_write;
|
||||
monitor.last_disk_update_time = current_time;
|
||||
|
||||
double load1, load5, load15;
|
||||
get_load_averages(&load1, &load5, &load15);
|
||||
history_deque_push(&monitor.load1_history, current_time, load1);
|
||||
history_deque_push(&monitor.load5_history, current_time, load5);
|
||||
history_deque_push(&monitor.load15_history, current_time, load15);
|
||||
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s != NULL; s = s->next) {
|
||||
double vhost_delta = current_time - s->last_update;
|
||||
if (vhost_delta >= 1.0) {
|
||||
double kbps = 0;
|
||||
if (s->last_update > 0) {
|
||||
long long bytes_diff = (s->bytes_sent - s->last_bytes_sent) + (s->bytes_recv - s->last_bytes_recv);
|
||||
kbps = bytes_diff / vhost_delta / 1024.0;
|
||||
}
|
||||
history_deque_push(&s->throughput_history, current_time, fmax(0, kbps));
|
||||
s->last_bytes_sent = s->bytes_sent;
|
||||
s->last_bytes_recv = s->bytes_recv;
|
||||
s->last_update = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
vhost_stats_t *stats = calloc(1, sizeof(vhost_stats_t));
|
||||
if (!stats) return NULL;
|
||||
static time_t last_db_save = 0;
|
||||
if (current_time - last_db_save >= 10) {
|
||||
save_stats_to_db();
|
||||
last_db_save = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(stats->vhost_name, vhost_name, sizeof(stats->vhost_name) - 1);
|
||||
stats->vhost_name[sizeof(stats->vhost_name) - 1] = '\0';
|
||||
vhost_stats_t* monitor_get_or_create_vhost_stats(const char *vhost_name) {
|
||||
if (!vhost_name || strlen(vhost_name) == 0) return NULL;
|
||||
|
||||
history_deque_init(&stats->throughput_history, 60);
|
||||
request_time_deque_init(&stats->request_times, 1000);
|
||||
histogram_init(&stats->latency_histogram);
|
||||
histogram_init(&stats->request_size_histogram);
|
||||
histogram_init(&stats->response_size_histogram);
|
||||
histogram_init(&stats->ttfb_histogram);
|
||||
histogram_init(&stats->upstream_connect_latency);
|
||||
rate_tracker_init(&stats->requests_per_second);
|
||||
for (vhost_stats_t *curr = monitor.vhost_stats_head; curr; curr = curr->next) {
|
||||
if (strcmp(curr->vhost_name, vhost_name) == 0) {
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
stats->next = monitor.vhost_stats_head;
|
||||
monitor.vhost_stats_head = stats;
|
||||
vhost_stats_t *new_stats = calloc(1, sizeof(vhost_stats_t));
|
||||
if (!new_stats) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stats;
|
||||
strncpy(new_stats->vhost_name, vhost_name, sizeof(new_stats->vhost_name) - 1);
|
||||
new_stats->last_update = time(NULL);
|
||||
history_deque_init(&new_stats->throughput_history, 60);
|
||||
request_time_deque_init(&new_stats->request_times, 100);
|
||||
new_stats->next = monitor.vhost_stats_head;
|
||||
monitor.vhost_stats_head = new_stats;
|
||||
|
||||
return new_stats;
|
||||
}
|
||||
|
||||
void monitor_record_request_start(vhost_stats_t *stats, int is_websocket) {
|
||||
if (!stats) return;
|
||||
stats->total_requests++;
|
||||
if (is_websocket) {
|
||||
stats->websocket_requests++;
|
||||
} else {
|
||||
stats->http_requests++;
|
||||
}
|
||||
rate_tracker_increment(&stats->requests_per_second);
|
||||
rate_tracker_increment(&monitor.global_rps);
|
||||
stats->total_requests++;
|
||||
}
|
||||
|
||||
void monitor_record_request_end(vhost_stats_t *stats, double start_time) {
|
||||
if (!stats) return;
|
||||
double now = get_current_time_seconds();
|
||||
double duration_ms = (now - start_time) * 1000.0;
|
||||
|
||||
histogram_add(&stats->latency_histogram, duration_ms);
|
||||
histogram_add(&monitor.global_latency, duration_ms);
|
||||
|
||||
request_time_deque_push(&stats->request_times, duration_ms);
|
||||
|
||||
int count = stats->request_times.count;
|
||||
if (count > 0) {
|
||||
double sum = 0;
|
||||
int start_idx = (stats->request_times.head - count + stats->request_times.capacity) % stats->request_times.capacity;
|
||||
for (int i = 0; i < count; i++) {
|
||||
int idx = (start_idx + i) % stats->request_times.capacity;
|
||||
sum += stats->request_times.times[idx];
|
||||
}
|
||||
stats->avg_request_time_ms = sum / count;
|
||||
if (!stats || start_time <= 0) return;
|
||||
struct timespec end_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||
double duration_ms = ((end_time.tv_sec + end_time.tv_nsec / 1e9) - start_time) * 1000.0;
|
||||
if (duration_ms >= 0 && duration_ms < 60000) {
|
||||
request_time_deque_push(&stats->request_times, duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,223 +518,3 @@ void monitor_record_bytes(vhost_stats_t *stats, long long sent, long long recv)
|
||||
stats->bytes_sent += sent;
|
||||
stats->bytes_recv += recv;
|
||||
}
|
||||
|
||||
http_method_t http_method_from_string(const char *method) {
|
||||
if (!method) return HTTP_METHOD_OTHER;
|
||||
if (strcmp(method, "GET") == 0) return HTTP_METHOD_GET;
|
||||
if (strcmp(method, "POST") == 0) return HTTP_METHOD_POST;
|
||||
if (strcmp(method, "PUT") == 0) return HTTP_METHOD_PUT;
|
||||
if (strcmp(method, "DELETE") == 0) return HTTP_METHOD_DELETE;
|
||||
if (strcmp(method, "PATCH") == 0) return HTTP_METHOD_PATCH;
|
||||
if (strcmp(method, "HEAD") == 0) return HTTP_METHOD_HEAD;
|
||||
if (strcmp(method, "OPTIONS") == 0) return HTTP_METHOD_OPTIONS;
|
||||
return HTTP_METHOD_OTHER;
|
||||
}
|
||||
|
||||
void monitor_record_method(vhost_stats_t *stats, http_method_t method) {
|
||||
if (!stats || method >= HTTP_METHOD_COUNT) return;
|
||||
stats->method_counts.counts[method]++;
|
||||
}
|
||||
|
||||
void monitor_record_status(vhost_stats_t *stats, int status_code) {
|
||||
if (!stats) return;
|
||||
if (status_code >= 100 && status_code < 200) stats->status_counts.status_1xx++;
|
||||
else if (status_code >= 200 && status_code < 300) stats->status_counts.status_2xx++;
|
||||
else if (status_code >= 300 && status_code < 400) stats->status_counts.status_3xx++;
|
||||
else if (status_code >= 400 && status_code < 500) stats->status_counts.status_4xx++;
|
||||
else if (status_code >= 500 && status_code < 600) stats->status_counts.status_5xx++;
|
||||
else stats->status_counts.status_unknown++;
|
||||
}
|
||||
|
||||
void monitor_record_request_size(vhost_stats_t *stats, long size) {
|
||||
if (!stats) return;
|
||||
histogram_add_with_bounds(&stats->request_size_histogram, (double)size, SIZE_BUCKET_BOUNDS);
|
||||
}
|
||||
|
||||
void monitor_record_response_size(vhost_stats_t *stats, long size) {
|
||||
if (!stats) return;
|
||||
histogram_add_with_bounds(&stats->response_size_histogram, (double)size, SIZE_BUCKET_BOUNDS);
|
||||
stats->response_bytes_total += size;
|
||||
}
|
||||
|
||||
void monitor_record_ttfb(vhost_stats_t *stats, double ttfb_ms) {
|
||||
if (!stats) return;
|
||||
histogram_add(&stats->ttfb_histogram, ttfb_ms);
|
||||
}
|
||||
|
||||
void monitor_record_upstream_connect(vhost_stats_t *stats, int success, double latency_ms) {
|
||||
if (!stats) return;
|
||||
if (success) {
|
||||
stats->upstream_connect_success++;
|
||||
histogram_add(&stats->upstream_connect_latency, latency_ms);
|
||||
} else {
|
||||
stats->upstream_connect_failures++;
|
||||
}
|
||||
}
|
||||
|
||||
void monitor_record_splice_transfer(vhost_stats_t *stats, long long bytes) {
|
||||
if (!stats) return;
|
||||
stats->splice_transfers++;
|
||||
stats->bytes_via_splice += (uint64_t)bytes;
|
||||
}
|
||||
|
||||
void monitor_record_buffer_transfer(vhost_stats_t *stats, long long bytes) {
|
||||
if (!stats) return;
|
||||
stats->buffered_transfers++;
|
||||
stats->bytes_via_buffer += (uint64_t)bytes;
|
||||
}
|
||||
|
||||
void monitor_record_connection_opened(vhost_stats_t *stats) {
|
||||
if (!stats) return;
|
||||
stats->connections_opened++;
|
||||
}
|
||||
|
||||
void monitor_record_connection_closed(vhost_stats_t *stats) {
|
||||
if (!stats) return;
|
||||
stats->connections_closed++;
|
||||
}
|
||||
|
||||
void monitor_record_keepalive_reuse(vhost_stats_t *stats) {
|
||||
if (!stats) return;
|
||||
stats->keep_alive_reused++;
|
||||
}
|
||||
|
||||
void monitor_record_error(vhost_stats_t *stats, int error_type) {
|
||||
if (!stats) return;
|
||||
switch (error_type) {
|
||||
case ERROR_TYPE_DNS:
|
||||
stats->dns_failures++;
|
||||
break;
|
||||
case ERROR_TYPE_SSL:
|
||||
stats->ssl_failures++;
|
||||
break;
|
||||
case ERROR_TYPE_TIMEOUT:
|
||||
stats->timeout_errors++;
|
||||
break;
|
||||
case ERROR_TYPE_CONNECTION:
|
||||
stats->connection_errors++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void monitor_compute_health_score(void) {
|
||||
double score = 100.0;
|
||||
|
||||
if (monitor.cpu_history.count > 0) {
|
||||
int idx = (monitor.cpu_history.head - 1 + monitor.cpu_history.capacity) % monitor.cpu_history.capacity;
|
||||
double cpu = monitor.cpu_history.points[idx].value;
|
||||
if (cpu > 90) score -= 30;
|
||||
else if (cpu > 80) score -= 15;
|
||||
else if (cpu > 70) score -= 5;
|
||||
}
|
||||
|
||||
uint64_t total_success = 0, total_errors = 0;
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s; s = s->next) {
|
||||
total_success += s->status_counts.status_2xx + s->status_counts.status_3xx;
|
||||
total_errors += s->status_counts.status_5xx;
|
||||
}
|
||||
|
||||
if (total_success + total_errors > 0) {
|
||||
double error_rate = (double)total_errors / (double)(total_success + total_errors);
|
||||
monitor.error_rate_1m = error_rate;
|
||||
if (error_rate > 0.1) score -= 40;
|
||||
else if (error_rate > 0.05) score -= 20;
|
||||
else if (error_rate > 0.01) score -= 10;
|
||||
}
|
||||
|
||||
double p99 = histogram_percentile(&monitor.global_latency, 0.99);
|
||||
if (p99 > 5000) score -= 20;
|
||||
else if (p99 > 2000) score -= 10;
|
||||
else if (p99 > 1000) score -= 5;
|
||||
|
||||
monitor.health_score = fmax(0, fmin(100, score));
|
||||
}
|
||||
|
||||
double monitor_get_current_rps(void) {
|
||||
return (double)rate_tracker_get_rps(&monitor.global_rps);
|
||||
}
|
||||
|
||||
void monitor_update(void) {
|
||||
double now = get_current_time_seconds();
|
||||
time_t now_t = (time_t)now;
|
||||
|
||||
double cpu = stats_get_cpu_usage();
|
||||
history_deque_push(&monitor.cpu_history, now, cpu);
|
||||
|
||||
double mem_gb = 0;
|
||||
stats_get_memory_usage(&mem_gb);
|
||||
history_deque_push(&monitor.memory_history, now, mem_gb);
|
||||
|
||||
double load1, load5, load15;
|
||||
stats_get_load_averages(&load1, &load5, &load15);
|
||||
history_deque_push(&monitor.load1_history, now, load1);
|
||||
history_deque_push(&monitor.load5_history, now, load5);
|
||||
history_deque_push(&monitor.load15_history, now, load15);
|
||||
|
||||
long long net_sent, net_recv;
|
||||
stats_get_network(&net_sent, &net_recv);
|
||||
double dt = now - monitor.last_net_update_time;
|
||||
if (dt > 0.5 && monitor.last_net_sent > 0) {
|
||||
double rx_kbps = (double)(net_recv - monitor.last_net_recv) / dt / 1024.0;
|
||||
double tx_kbps = (double)(net_sent - monitor.last_net_sent) / dt / 1024.0;
|
||||
network_history_deque_push(&monitor.network_history, now, rx_kbps, tx_kbps);
|
||||
}
|
||||
monitor.last_net_sent = net_sent;
|
||||
monitor.last_net_recv = net_recv;
|
||||
monitor.last_net_update_time = now;
|
||||
|
||||
long long disk_read, disk_write;
|
||||
stats_get_disk(&disk_read, &disk_write);
|
||||
double disk_dt = now - monitor.last_disk_update_time;
|
||||
if (disk_dt > 0.5 && monitor.last_disk_read > 0) {
|
||||
double read_mbps = (double)(disk_read - monitor.last_disk_read) * 512.0 / disk_dt / (1024.0 * 1024.0);
|
||||
double write_mbps = (double)(disk_write - monitor.last_disk_write) * 512.0 / disk_dt / (1024.0 * 1024.0);
|
||||
disk_history_deque_push(&monitor.disk_history, now, read_mbps, write_mbps);
|
||||
}
|
||||
monitor.last_disk_read = disk_read;
|
||||
monitor.last_disk_write = disk_write;
|
||||
monitor.last_disk_update_time = now;
|
||||
|
||||
double current_rps = monitor_get_current_rps();
|
||||
if (current_rps > monitor.peak_rps) {
|
||||
monitor.peak_rps = current_rps;
|
||||
monitor.peak_rps_time = now_t;
|
||||
}
|
||||
|
||||
for (vhost_stats_t *s = monitor.vhost_stats_head; s; s = s->next) {
|
||||
if (s->last_update > 0) {
|
||||
double vhost_dt = now - s->last_update;
|
||||
if (vhost_dt >= 1.0) {
|
||||
double throughput = (double)((s->bytes_sent - s->last_bytes_sent) +
|
||||
(s->bytes_recv - s->last_bytes_recv)) / vhost_dt / 1024.0;
|
||||
history_deque_push(&s->throughput_history, now, throughput);
|
||||
s->last_bytes_sent = s->bytes_sent;
|
||||
s->last_bytes_recv = s->bytes_recv;
|
||||
s->last_update = now;
|
||||
}
|
||||
} else {
|
||||
s->last_bytes_sent = s->bytes_sent;
|
||||
s->last_bytes_recv = s->bytes_recv;
|
||||
s->last_update = now;
|
||||
}
|
||||
|
||||
uint32_t rps = rate_tracker_get_rps(&s->requests_per_second);
|
||||
if ((double)rps > s->peak_rps) {
|
||||
s->peak_rps = (double)rps;
|
||||
s->peak_rps_time = now_t;
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor.db) {
|
||||
char sql[512];
|
||||
snprintf(sql, sizeof(sql),
|
||||
"INSERT INTO stats_history (timestamp, cpu_percent, memory_gb, active_connections, requests_per_second) "
|
||||
"VALUES (%ld, %.2f, %.2f, %d, %.2f)",
|
||||
now_t, cpu, mem_gb, monitor.active_connections, current_rps);
|
||||
sqlite3_exec(monitor.db, sql, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void monitor_update_connection_states(void) {
|
||||
memset(monitor.connections_by_state, 0, sizeof(monitor.connections_by_state));
|
||||
}
|
||||
|
||||
Executable → Regular
-37
@@ -22,41 +22,4 @@ void disk_history_deque_push(disk_history_deque_t *dq, double time, double read_
|
||||
void request_time_deque_init(request_time_deque_t *dq, int capacity);
|
||||
void request_time_deque_push(request_time_deque_t *dq, double time_ms);
|
||||
|
||||
void histogram_init(histogram_t *h);
|
||||
void histogram_add(histogram_t *h, double value);
|
||||
double histogram_percentile(histogram_t *h, double p);
|
||||
double histogram_mean(histogram_t *h);
|
||||
|
||||
void rate_tracker_init(rate_tracker_t *rt);
|
||||
void rate_tracker_increment(rate_tracker_t *rt);
|
||||
uint32_t rate_tracker_get_rps(rate_tracker_t *rt);
|
||||
uint32_t rate_tracker_get_total_last_minute(rate_tracker_t *rt);
|
||||
|
||||
http_method_t http_method_from_string(const char *method);
|
||||
void monitor_record_method(vhost_stats_t *stats, http_method_t method);
|
||||
void monitor_record_status(vhost_stats_t *stats, int status_code);
|
||||
void monitor_record_request_size(vhost_stats_t *stats, long size);
|
||||
void monitor_record_response_size(vhost_stats_t *stats, long size);
|
||||
void monitor_record_ttfb(vhost_stats_t *stats, double ttfb_ms);
|
||||
void monitor_record_upstream_connect(vhost_stats_t *stats, int success, double latency_ms);
|
||||
void monitor_record_splice_transfer(vhost_stats_t *stats, long long bytes);
|
||||
void monitor_record_buffer_transfer(vhost_stats_t *stats, long long bytes);
|
||||
void monitor_record_connection_opened(vhost_stats_t *stats);
|
||||
void monitor_record_connection_closed(vhost_stats_t *stats);
|
||||
void monitor_record_keepalive_reuse(vhost_stats_t *stats);
|
||||
void monitor_record_error(vhost_stats_t *stats, int error_type);
|
||||
|
||||
void monitor_compute_health_score(void);
|
||||
void monitor_update_connection_states(void);
|
||||
double monitor_get_current_rps(void);
|
||||
|
||||
#define ERROR_TYPE_DNS 0
|
||||
#define ERROR_TYPE_SSL 1
|
||||
#define ERROR_TYPE_TIMEOUT 2
|
||||
#define ERROR_TYPE_CONNECTION 3
|
||||
|
||||
extern const double LATENCY_BUCKET_BOUNDS[HISTOGRAM_BUCKETS];
|
||||
extern const char* LATENCY_BUCKET_LABELS[HISTOGRAM_BUCKETS];
|
||||
extern const double SIZE_BUCKET_BOUNDS[HISTOGRAM_BUCKETS];
|
||||
|
||||
#endif
|
||||
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
+1
-2
@@ -74,8 +74,7 @@ int rate_limit_check(const char *client_ip) {
|
||||
|
||||
rate_limit_entry_t *new_entry = calloc(1, sizeof(rate_limit_entry_t));
|
||||
if (!new_entry) {
|
||||
log_error("Rate limit entry allocation failed, denying request for safety");
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
strncpy(new_entry->client_ip, client_ip, sizeof(new_entry->client_ip) - 1);
|
||||
|
||||
Executable → Regular
@@ -1,55 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "rate_tracker.h"
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
void rate_tracker_init(rate_tracker_t *rt) {
|
||||
if (!rt) return;
|
||||
memset(rt, 0, sizeof(rate_tracker_t));
|
||||
rt->slot_start = time(NULL);
|
||||
}
|
||||
|
||||
void rate_tracker_increment(rate_tracker_t *rt) {
|
||||
if (!rt) return;
|
||||
time_t now = time(NULL);
|
||||
int slot = (int)(now % RATE_TRACKER_SLOTS);
|
||||
|
||||
if (now != rt->slot_start) {
|
||||
int slots_to_clear = (int)(now - rt->slot_start);
|
||||
if (slots_to_clear >= RATE_TRACKER_SLOTS) {
|
||||
memset(rt->counts, 0, sizeof(rt->counts));
|
||||
} else {
|
||||
for (int i = 1; i <= slots_to_clear; i++) {
|
||||
int clear_slot = (rt->current_slot + i) % RATE_TRACKER_SLOTS;
|
||||
rt->counts[clear_slot] = 0;
|
||||
}
|
||||
}
|
||||
rt->current_slot = slot;
|
||||
rt->slot_start = now;
|
||||
}
|
||||
|
||||
rt->counts[slot]++;
|
||||
}
|
||||
|
||||
uint32_t rate_tracker_get_rps(rate_tracker_t *rt) {
|
||||
if (!rt) return 0;
|
||||
time_t now = time(NULL);
|
||||
int prev_slot = (int)((now - 1) % RATE_TRACKER_SLOTS);
|
||||
if ((now - rt->slot_start) > RATE_TRACKER_SLOTS) return 0;
|
||||
return rt->counts[prev_slot];
|
||||
}
|
||||
|
||||
uint32_t rate_tracker_get_total_last_minute(rate_tracker_t *rt) {
|
||||
if (!rt) return 0;
|
||||
time_t now = time(NULL);
|
||||
if ((now - rt->slot_start) > RATE_TRACKER_SLOTS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t total = 0;
|
||||
for (int i = 0; i < RATE_TRACKER_SLOTS; i++) {
|
||||
total += rt->counts[i];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_RATE_TRACKER_H
|
||||
#define RPROXY_RATE_TRACKER_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void rate_tracker_init(rate_tracker_t *rt);
|
||||
void rate_tracker_increment(rate_tracker_t *rt);
|
||||
uint32_t rate_tracker_get_rps(rate_tracker_t *rt);
|
||||
uint32_t rate_tracker_get_total_last_minute(rate_tracker_t *rt);
|
||||
|
||||
#endif
|
||||
@@ -1,76 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "socket_utils.h"
|
||||
#include "logging.h"
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
int socket_set_non_blocking(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
log_error("fcntl F_GETFL failed");
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
log_error("fcntl F_SETFL failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void socket_set_tcp_keepalive(int fd) {
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) < 0) {
|
||||
log_debug("setsockopt SO_KEEPALIVE failed for fd %d: %s", fd, strerror(errno));
|
||||
}
|
||||
int idle = 60;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)) < 0) {
|
||||
log_debug("setsockopt TCP_KEEPIDLE failed for fd %d: %s", fd, strerror(errno));
|
||||
}
|
||||
int interval = 10;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)) < 0) {
|
||||
log_debug("setsockopt TCP_KEEPINTVL failed for fd %d: %s", fd, strerror(errno));
|
||||
}
|
||||
int maxpkt = 6;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(maxpkt)) < 0) {
|
||||
log_debug("setsockopt TCP_KEEPCNT failed for fd %d: %s", fd, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void socket_set_tcp_nodelay(int fd) {
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) < 0) {
|
||||
log_debug("setsockopt TCP_NODELAY failed for fd %d: %s", fd, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TCP_QUICKACK
|
||||
void socket_set_tcp_quickack(int fd) {
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes)) < 0) {
|
||||
log_debug("setsockopt TCP_QUICKACK failed for fd %d: %s", fd, strerror(errno));
|
||||
}
|
||||
}
|
||||
#else
|
||||
void socket_set_tcp_quickack(int fd) {
|
||||
(void)fd;
|
||||
}
|
||||
#endif
|
||||
|
||||
void socket_optimize(int fd, int is_upstream) {
|
||||
socket_set_tcp_nodelay(fd);
|
||||
socket_set_tcp_keepalive(fd);
|
||||
#ifdef TCP_QUICKACK
|
||||
if (!is_upstream) {
|
||||
socket_set_tcp_quickack(fd);
|
||||
}
|
||||
#endif
|
||||
int sndbuf = is_upstream ? 262144 : 131072;
|
||||
int rcvbuf = is_upstream ? 524288 : 131072;
|
||||
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_SOCKET_UTILS_H
|
||||
#define RPROXY_SOCKET_UTILS_H
|
||||
|
||||
int socket_set_non_blocking(int fd);
|
||||
void socket_set_tcp_keepalive(int fd);
|
||||
void socket_set_tcp_nodelay(int fd);
|
||||
void socket_set_tcp_quickack(int fd);
|
||||
void socket_optimize(int fd, int is_upstream);
|
||||
|
||||
#endif
|
||||
Executable → Regular
+1
-22
@@ -2,12 +2,11 @@
|
||||
#include "logging.h"
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
static int g_ssl_verify_enabled = 1;
|
||||
static int g_ssl_verify_enabled = 0;
|
||||
static char g_ca_file[512] = "";
|
||||
static char g_ca_path[512] = "";
|
||||
|
||||
@@ -64,26 +63,6 @@ void ssl_init(void) {
|
||||
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
|
||||
if (SSL_CTX_set_cipher_list(ssl_ctx,
|
||||
"ECDHE+AESGCM:DHE+AESGCM:ECDHE+CHACHA20:DHE+CHACHA20:!aNULL:!MD5:!DSS") != 1) {
|
||||
log_info("Warning: Could not set preferred cipher list, using defaults");
|
||||
}
|
||||
}
|
||||
|
||||
int ssl_set_hostname(SSL *ssl, const char *hostname) {
|
||||
if (!ssl || !hostname || hostname[0] == '\0') return 0;
|
||||
|
||||
SSL_set_tlsext_host_name(ssl, hostname);
|
||||
|
||||
if (g_ssl_verify_enabled) {
|
||||
SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
||||
if (SSL_set1_host(ssl, hostname) != 1) {
|
||||
log_debug("Failed to set hostname verification for %s", hostname);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ssl_cleanup(void) {
|
||||
|
||||
Executable → Regular
-1
@@ -10,7 +10,6 @@ void ssl_set_ca_file(const char *path);
|
||||
void ssl_set_ca_path(const char *path);
|
||||
void ssl_init(void);
|
||||
void ssl_cleanup(void);
|
||||
int ssl_set_hostname(SSL *ssl, const char *hostname);
|
||||
int ssl_do_handshake(connection_t *conn);
|
||||
int ssl_read(connection_t *conn, char *buf, size_t len);
|
||||
int ssl_write(connection_t *conn, const char *buf, size_t len);
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "stats_collector.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
static long long g_prev_user = 0;
|
||||
static long long g_prev_nice = 0;
|
||||
static long long g_prev_system = 0;
|
||||
static long long g_prev_idle = 0;
|
||||
|
||||
double stats_get_cpu_usage(void) {
|
||||
long long user, nice, system, idle, iowait, irq, softirq;
|
||||
|
||||
FILE *f = fopen("/proc/stat", "r");
|
||||
if (!f) return 0.0;
|
||||
|
||||
if (fscanf(f, "cpu %lld %lld %lld %lld %lld %lld %lld",
|
||||
&user, &nice, &system, &idle, &iowait, &irq, &softirq) != 7) {
|
||||
fclose(f);
|
||||
return 0.0;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
long long prev_total = g_prev_user + g_prev_nice + g_prev_system + g_prev_idle;
|
||||
long long total = user + nice + system + idle;
|
||||
long long totald = total - prev_total;
|
||||
long long idled = idle - g_prev_idle;
|
||||
|
||||
g_prev_user = user;
|
||||
g_prev_nice = nice;
|
||||
g_prev_system = system;
|
||||
g_prev_idle = idle;
|
||||
|
||||
return totald == 0 ? 0.0 : (double)(totald - idled) * 100.0 / totald;
|
||||
}
|
||||
|
||||
void stats_get_memory_usage(double *used_gb) {
|
||||
struct sysinfo info;
|
||||
if (sysinfo(&info) != 0) {
|
||||
*used_gb = 0;
|
||||
return;
|
||||
}
|
||||
*used_gb = (double)(info.totalram - info.freeram - info.bufferram) * info.mem_unit / (1024.0 * 1024.0 * 1024.0);
|
||||
}
|
||||
|
||||
void stats_get_network(long long *bytes_sent, long long *bytes_recv) {
|
||||
FILE *f = fopen("/proc/net/dev", "r");
|
||||
if (!f) {
|
||||
*bytes_sent = 0;
|
||||
*bytes_recv = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
if (!fgets(line, sizeof(line), f) || !fgets(line, sizeof(line), f)) {
|
||||
fclose(f);
|
||||
*bytes_sent = 0;
|
||||
*bytes_recv = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
long long total_recv = 0, total_sent = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char iface[32];
|
||||
long long r, t;
|
||||
if (sscanf(line, "%31[^:]: %lld %*d %*d %*d %*d %*d %*d %*d %lld", iface, &r, &t) == 3) {
|
||||
char *trimmed = iface;
|
||||
while (*trimmed == ' ') trimmed++;
|
||||
if (strcmp(trimmed, "lo") != 0) {
|
||||
total_recv += r;
|
||||
total_sent += t;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
*bytes_sent = total_sent;
|
||||
*bytes_recv = total_recv;
|
||||
}
|
||||
|
||||
void stats_get_disk(long long *sectors_read, long long *sectors_written) {
|
||||
FILE *f = fopen("/proc/diskstats", "r");
|
||||
if (!f) {
|
||||
*sectors_read = 0;
|
||||
*sectors_written = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char line[2048];
|
||||
long long total_read = 0, total_written = 0;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char device[64];
|
||||
long long sr = 0, sw = 0;
|
||||
|
||||
char major[16], minor[16], dev[64];
|
||||
char rc[32], rm[32], srd[32], rtm[32], rtm2[32], wc[32], wm[32], swd[32];
|
||||
int nfields = sscanf(line, "%15s %15s %63s %31s %31s %31s %31s %31s %31s %31s %31s %31s",
|
||||
major, minor, dev, rc, rm, srd, rtm, rtm2, wc, wm, swd, swd);
|
||||
if (nfields >= 11) {
|
||||
strncpy(device, dev, sizeof(device) - 1);
|
||||
device[sizeof(device) - 1] = '\0';
|
||||
char *endptr;
|
||||
sr = strtoll(srd, &endptr, 10);
|
||||
if (endptr == srd) sr = 0;
|
||||
sw = strtoll(swd, &endptr, 10);
|
||||
if (endptr == swd) sw = 0;
|
||||
|
||||
if (strncmp(device, "loop", 4) != 0 && strncmp(device, "ram", 3) != 0) {
|
||||
size_t len = strlen(device);
|
||||
if ((strncmp(device, "sd", 2) == 0 && len == 3) ||
|
||||
(strncmp(device, "nvme", 4) == 0 && strstr(device, "n1p") == NULL) ||
|
||||
(strncmp(device, "vd", 2) == 0 && len == 3) ||
|
||||
(strncmp(device, "hd", 2) == 0 && len == 3)) {
|
||||
total_read += sr;
|
||||
total_written += sw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
*sectors_read = total_read;
|
||||
*sectors_written = total_written;
|
||||
}
|
||||
|
||||
void stats_get_load_averages(double *load1, double *load5, double *load15) {
|
||||
FILE *f = fopen("/proc/loadavg", "r");
|
||||
if (!f) {
|
||||
*load1 = *load5 = *load15 = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fscanf(f, "%lf %lf %lf", load1, load5, load15) != 3) {
|
||||
*load1 = *load5 = *load15 = 0.0;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_STATS_COLLECTOR_H
|
||||
#define RPROXY_STATS_COLLECTOR_H
|
||||
|
||||
double stats_get_cpu_usage(void);
|
||||
void stats_get_memory_usage(double *used_gb);
|
||||
void stats_get_network(long long *bytes_sent, long long *bytes_recv);
|
||||
void stats_get_disk(long long *sectors_read, long long *sectors_written);
|
||||
void stats_get_load_averages(double *load1, double *load5, double *load15);
|
||||
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "time_utils.h"
|
||||
#include <string.h>
|
||||
|
||||
void time_format_http_date(time_t t, char *buf, size_t buf_size) {
|
||||
if (!buf || buf_size == 0) return;
|
||||
|
||||
struct tm *gmt = gmtime(&t);
|
||||
if (gmt) {
|
||||
strftime(buf, buf_size, "%a, %d %b %Y %H:%M:%S GMT", gmt);
|
||||
} else {
|
||||
strncpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_size - 1);
|
||||
buf[buf_size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void time_format_http_date_now(char *buf, size_t buf_size) {
|
||||
time_format_http_date(time(NULL), buf, buf_size);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_TIME_UTILS_H
|
||||
#define RPROXY_TIME_UTILS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
void time_format_http_date(time_t t, char *buf, size_t buf_size);
|
||||
void time_format_http_date_now(char *buf, size_t buf_size);
|
||||
|
||||
#endif
|
||||
Executable → Regular
-102
@@ -6,7 +6,6 @@
|
||||
#endif
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <sqlite3.h>
|
||||
@@ -35,9 +34,6 @@
|
||||
#define SSL_HANDSHAKE_TIMEOUT_SEC 10
|
||||
#define MAX_CONNECTIONS_PER_IP 100
|
||||
#define HOSTNAME_MAX_LEN 256
|
||||
#define HISTOGRAM_BUCKETS 16
|
||||
#define RATE_TRACKER_SLOTS 60
|
||||
#define HTTP_METHOD_COUNT 8
|
||||
|
||||
typedef enum {
|
||||
CONN_TYPE_UNUSED,
|
||||
@@ -54,45 +50,6 @@ typedef enum {
|
||||
CLIENT_STATE_CLOSING
|
||||
} client_state_t;
|
||||
|
||||
typedef enum {
|
||||
HTTP_METHOD_GET = 0,
|
||||
HTTP_METHOD_POST,
|
||||
HTTP_METHOD_PUT,
|
||||
HTTP_METHOD_DELETE,
|
||||
HTTP_METHOD_PATCH,
|
||||
HTTP_METHOD_HEAD,
|
||||
HTTP_METHOD_OPTIONS,
|
||||
HTTP_METHOD_OTHER
|
||||
} http_method_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t buckets[HISTOGRAM_BUCKETS];
|
||||
uint32_t overflow;
|
||||
uint64_t total_count;
|
||||
double sum;
|
||||
double min_value;
|
||||
double max_value;
|
||||
} histogram_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t counts[HTTP_METHOD_COUNT];
|
||||
} method_counter_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t status_1xx;
|
||||
uint64_t status_2xx;
|
||||
uint64_t status_3xx;
|
||||
uint64_t status_4xx;
|
||||
uint64_t status_5xx;
|
||||
uint64_t status_unknown;
|
||||
} status_counter_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t counts[RATE_TRACKER_SLOTS];
|
||||
int current_slot;
|
||||
time_t slot_start;
|
||||
} rate_tracker_t;
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t capacity;
|
||||
@@ -122,7 +79,6 @@ typedef struct connection_s {
|
||||
conn_type_t type;
|
||||
client_state_t state;
|
||||
int fd;
|
||||
char client_ip[64];
|
||||
struct connection_s *pair;
|
||||
struct vhost_stats_s *vhost_stats;
|
||||
buffer_t read_buf;
|
||||
@@ -143,11 +99,6 @@ typedef struct connection_s {
|
||||
int response_headers_parsed;
|
||||
long original_content_length;
|
||||
long content_length_delta;
|
||||
char *patch_buf;
|
||||
size_t patch_buf_capacity;
|
||||
int splice_pipe[2];
|
||||
int can_splice;
|
||||
uint32_t epoll_events;
|
||||
} connection_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -241,43 +192,6 @@ typedef struct vhost_stats_s {
|
||||
double last_update;
|
||||
history_deque_t throughput_history;
|
||||
request_time_deque_t request_times;
|
||||
|
||||
histogram_t latency_histogram;
|
||||
histogram_t request_size_histogram;
|
||||
histogram_t response_size_histogram;
|
||||
histogram_t ttfb_histogram;
|
||||
histogram_t upstream_connect_latency;
|
||||
|
||||
method_counter_t method_counts;
|
||||
status_counter_t status_counts;
|
||||
rate_tracker_t requests_per_second;
|
||||
|
||||
uint64_t keep_alive_reused;
|
||||
uint64_t connections_opened;
|
||||
uint64_t connections_closed;
|
||||
|
||||
uint64_t splice_transfers;
|
||||
uint64_t buffered_transfers;
|
||||
uint64_t bytes_via_splice;
|
||||
uint64_t bytes_via_buffer;
|
||||
|
||||
uint64_t upstream_connect_success;
|
||||
uint64_t upstream_connect_failures;
|
||||
uint64_t upstream_retries;
|
||||
|
||||
uint64_t rate_limit_rejections;
|
||||
uint64_t auth_failures;
|
||||
uint64_t patch_blocks;
|
||||
|
||||
uint64_t dns_failures;
|
||||
uint64_t ssl_failures;
|
||||
uint64_t timeout_errors;
|
||||
uint64_t connection_errors;
|
||||
|
||||
double peak_rps;
|
||||
time_t peak_rps_time;
|
||||
long long response_bytes_total;
|
||||
|
||||
struct vhost_stats_s *next;
|
||||
} vhost_stats_t;
|
||||
|
||||
@@ -300,22 +214,6 @@ typedef struct {
|
||||
double last_disk_update_time;
|
||||
vhost_stats_t *vhost_stats_head;
|
||||
sqlite3 *db;
|
||||
|
||||
histogram_t global_latency;
|
||||
histogram_t connection_lifetime;
|
||||
rate_tracker_t global_rps;
|
||||
|
||||
uint64_t total_connections_accepted;
|
||||
uint64_t connections_by_state[5];
|
||||
|
||||
uint64_t total_rate_limit_checks;
|
||||
uint64_t total_rate_limit_blocks;
|
||||
|
||||
time_t uptime_start;
|
||||
double health_score;
|
||||
double error_rate_1m;
|
||||
double peak_rps;
|
||||
time_t peak_rps_time;
|
||||
} system_monitor_t;
|
||||
|
||||
#endif
|
||||
|
||||
-280
@@ -1,280 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include "upstream.h"
|
||||
#include "buffer.h"
|
||||
#include "logging.h"
|
||||
#include "config.h"
|
||||
#include "monitor.h"
|
||||
#include "http.h"
|
||||
#include "http_response.h"
|
||||
#include "ssl_handler.h"
|
||||
#include "socket_utils.h"
|
||||
#include "epoll_utils.h"
|
||||
#include "patch.h"
|
||||
#include "connection.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
extern connection_t connections[MAX_FDS];
|
||||
extern time_t cached_time;
|
||||
|
||||
int upstream_try_connect(struct sockaddr_in *addr, int *out_fd) {
|
||||
int up_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (up_fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (up_fd >= MAX_FDS) {
|
||||
close(up_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
socket_set_non_blocking(up_fd);
|
||||
socket_optimize(up_fd, 1);
|
||||
|
||||
int connect_result = connect(up_fd, (struct sockaddr *)addr, sizeof(*addr));
|
||||
if (connect_result < 0 && errno != EINPROGRESS) {
|
||||
close(up_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out_fd = up_fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void upstream_cleanup_partial(connection_t *up, int up_fd, connection_t *client, int free_read, int free_write) {
|
||||
if (free_read) buffer_free(&up->read_buf);
|
||||
if (free_write) buffer_free(&up->write_buf);
|
||||
if (up->config) config_ref_dec(up->config);
|
||||
close(up_fd);
|
||||
memset(up, 0, sizeof(connection_t));
|
||||
up->type = CONN_TYPE_UNUSED;
|
||||
up->fd = -1;
|
||||
if (client) client->pair = NULL;
|
||||
}
|
||||
|
||||
static char *upstream_rewrite_host_header(const char *data, size_t data_len, route_config_t *route, size_t *new_len) {
|
||||
char new_host_header[512];
|
||||
const char *old_host_header_start = NULL;
|
||||
const char *old_host_header_end = NULL;
|
||||
|
||||
if (!http_find_header_line_bounds(data, data_len, "Host", &old_host_header_start, &old_host_header_end)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int is_default_port = (route->use_ssl && route->upstream_port == 443) ||
|
||||
(!route->use_ssl && route->upstream_port == 80);
|
||||
if (is_default_port) {
|
||||
snprintf(new_host_header, sizeof(new_host_header), "Host: %s\r\n", route->upstream_host);
|
||||
} else {
|
||||
snprintf(new_host_header, sizeof(new_host_header), "Host: %s:%d\r\n", route->upstream_host, route->upstream_port);
|
||||
}
|
||||
|
||||
size_t new_host_len = strlen(new_host_header);
|
||||
size_t old_host_len = (size_t)(old_host_header_end - old_host_header_start);
|
||||
*new_len = data_len - old_host_len + new_host_len;
|
||||
|
||||
char *modified = malloc(*new_len + 1);
|
||||
if (!modified) return NULL;
|
||||
|
||||
char *p = modified;
|
||||
size_t prefix_len = (size_t)(old_host_header_start - data);
|
||||
memcpy(p, data, prefix_len);
|
||||
p += prefix_len;
|
||||
memcpy(p, new_host_header, new_host_len);
|
||||
p += new_host_len;
|
||||
size_t suffix_len = data_len - (size_t)(old_host_header_end - data);
|
||||
memcpy(p, old_host_header_end, suffix_len);
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
void upstream_connect(connection_t *client, const char *data, size_t data_len) {
|
||||
if (!client || !data) return;
|
||||
|
||||
app_config_t *current_config = config;
|
||||
if (!current_config) {
|
||||
http_response_send_error(client, 503, "Service Unavailable", "Configuration not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
route_config_t *route = config_find_route(client->request.host);
|
||||
if (!route) {
|
||||
http_response_send_error(client, 502, "Bad Gateway", "No route configured for this host");
|
||||
return;
|
||||
}
|
||||
|
||||
client->config = current_config;
|
||||
config_ref_inc(client->config);
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons((uint16_t)route->upstream_port);
|
||||
|
||||
if (inet_pton(AF_INET, route->upstream_host, &addr.sin_addr) <= 0) {
|
||||
struct addrinfo hints, *result;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
int gai_err = getaddrinfo(route->upstream_host, NULL, &hints, &result);
|
||||
if (gai_err != 0 || !result || !result->ai_addr) {
|
||||
if (gai_err == 0 && result) freeaddrinfo(result);
|
||||
log_debug("DNS resolution failed for %s: %s", route->upstream_host,
|
||||
gai_err ? gai_strerror(gai_err) : "no address returned");
|
||||
if (client->vhost_stats) {
|
||||
monitor_record_error(client->vhost_stats, ERROR_TYPE_DNS);
|
||||
}
|
||||
http_response_send_error(client, 502, "Bad Gateway", "Cannot resolve upstream hostname");
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in *resolved = (struct sockaddr_in *)result->ai_addr;
|
||||
addr.sin_addr = resolved->sin_addr;
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
int up_fd = -1;
|
||||
int retry_count = 0;
|
||||
|
||||
while (retry_count < MAX_UPSTREAM_RETRIES) {
|
||||
if (upstream_try_connect(&addr, &up_fd) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
retry_count++;
|
||||
if (retry_count < MAX_UPSTREAM_RETRIES) {
|
||||
log_debug("Upstream connection attempt %d failed for %s:%d, retrying...",
|
||||
retry_count, route->upstream_host, route->upstream_port);
|
||||
usleep(UPSTREAM_RETRY_DELAY_MS * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (up_fd < 0) {
|
||||
log_debug("All %d connection attempts failed for %s:%d",
|
||||
MAX_UPSTREAM_RETRIES, route->upstream_host, route->upstream_port);
|
||||
if (client->vhost_stats) {
|
||||
monitor_record_upstream_connect(client->vhost_stats, 0, 0);
|
||||
}
|
||||
http_response_send_error(client, 502, "Bad Gateway", "Failed to connect to upstream");
|
||||
return;
|
||||
}
|
||||
|
||||
if (client->vhost_stats) {
|
||||
monitor_record_upstream_connect(client->vhost_stats, 1, 0);
|
||||
if (retry_count > 0) {
|
||||
client->vhost_stats->upstream_retries += (uint64_t)retry_count;
|
||||
}
|
||||
}
|
||||
|
||||
if (client->pair) {
|
||||
log_debug("Closing existing upstream fd %d before new connection for client fd %d", client->pair->fd, client->fd);
|
||||
connection_close(client->pair->fd);
|
||||
}
|
||||
|
||||
epoll_utils_add(up_fd, EPOLLIN | EPOLLOUT);
|
||||
|
||||
connection_t *up = &connections[up_fd];
|
||||
memset(up, 0, sizeof(connection_t));
|
||||
up->type = CONN_TYPE_UPSTREAM;
|
||||
up->fd = up_fd;
|
||||
up->last_activity = cached_time;
|
||||
up->splice_pipe[0] = -1;
|
||||
up->splice_pipe[1] = -1;
|
||||
|
||||
client->pair = up;
|
||||
up->pair = client;
|
||||
up->vhost_stats = client->vhost_stats;
|
||||
up->route = route;
|
||||
client->route = route;
|
||||
up->config = client->config;
|
||||
config_ref_inc(up->config);
|
||||
|
||||
int use_splice = !patch_has_rules(&route->patches) && !route->use_ssl;
|
||||
if (use_splice) {
|
||||
if (pipe(up->splice_pipe) == 0) {
|
||||
up->can_splice = 1;
|
||||
client->can_splice = 1;
|
||||
client->splice_pipe[0] = up->splice_pipe[0];
|
||||
client->splice_pipe[1] = up->splice_pipe[1];
|
||||
} else {
|
||||
up->splice_pipe[0] = -1;
|
||||
up->splice_pipe[1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer_init(&up->read_buf, CHUNK_SIZE) < 0) {
|
||||
upstream_cleanup_partial(up, up_fd, client, 0, 0);
|
||||
http_response_send_error(client, 502, "Bad Gateway", "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
if (buffer_init(&up->write_buf, CHUNK_SIZE) < 0) {
|
||||
upstream_cleanup_partial(up, up_fd, client, 1, 0);
|
||||
http_response_send_error(client, 502, "Bad Gateway", "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
char *data_to_send = (char *)data;
|
||||
size_t len_to_send = data_len;
|
||||
char *modified_request = NULL;
|
||||
|
||||
if (route->rewrite_host) {
|
||||
modified_request = upstream_rewrite_host_header(data, data_len, route, &len_to_send);
|
||||
if (modified_request) {
|
||||
data_to_send = modified_request;
|
||||
log_debug("Rewrote Host header to %s for route %s", route->upstream_host, route->hostname);
|
||||
} else {
|
||||
log_debug("Failed to rewrite Host header for route %s (Host header not found?)", route->hostname);
|
||||
}
|
||||
} else {
|
||||
log_debug("Host rewrite disabled for route %s", route->hostname);
|
||||
}
|
||||
|
||||
if (buffer_ensure_capacity(&up->write_buf, len_to_send) < 0) {
|
||||
if (modified_request) free(modified_request);
|
||||
upstream_cleanup_partial(up, up_fd, client, 1, 1);
|
||||
http_response_send_error(client, 502, "Bad Gateway", "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
memcpy(up->write_buf.data, data_to_send, len_to_send);
|
||||
up->write_buf.tail = len_to_send;
|
||||
|
||||
if (modified_request) {
|
||||
free(modified_request);
|
||||
}
|
||||
|
||||
if (route->use_ssl) {
|
||||
up->ssl = SSL_new(ssl_ctx);
|
||||
if (!up->ssl) {
|
||||
upstream_cleanup_partial(up, up_fd, client, 1, 1);
|
||||
http_response_send_error(client, 502, "Bad Gateway", "SSL initialization failed");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *sni_hostname = route->rewrite_host ? route->upstream_host : client->request.host;
|
||||
ssl_set_hostname(up->ssl, sni_hostname);
|
||||
SSL_set_fd(up->ssl, up_fd);
|
||||
SSL_set_connect_state(up->ssl);
|
||||
|
||||
up->ssl_handshake_done = 0;
|
||||
up->ssl_handshake_start = time(NULL);
|
||||
|
||||
log_debug("Setting SNI to: %s for upstream %s:%d",
|
||||
sni_hostname, route->upstream_host, route->upstream_port);
|
||||
}
|
||||
|
||||
up->state = CLIENT_STATE_FORWARDING;
|
||||
|
||||
log_debug("Connecting to upstream %s:%d on fd %d (SSL: %s)",
|
||||
route->upstream_host, route->upstream_port, up_fd, route->use_ssl ? "yes" : "no");
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef RPROXY_UPSTREAM_H
|
||||
#define RPROXY_UPSTREAM_H
|
||||
|
||||
#include "types.h"
|
||||
#include <netinet/in.h>
|
||||
|
||||
void upstream_connect(connection_t *client, const char *data, size_t data_len);
|
||||
int upstream_try_connect(struct sockaddr_in *addr, int *out_fd);
|
||||
void upstream_cleanup_partial(connection_t *up, int up_fd, connection_t *client, int free_read, int free_write);
|
||||
|
||||
#endif
|
||||
Executable → Regular
-75
@@ -85,78 +85,6 @@ void test_auth_route_basic_auth(void) {
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_auth_route_full_flow(void) {
|
||||
TEST_SUITE_BEGIN("Auth Route Full Flow");
|
||||
|
||||
route_config_t route;
|
||||
memset(&route, 0, sizeof(route));
|
||||
route.use_auth = 1;
|
||||
strcpy(route.username, "routeuser");
|
||||
strcpy(route.password_hash, "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8");
|
||||
|
||||
char error[256];
|
||||
|
||||
int result = auth_check_route_basic_auth(&route, "Basic cm91dGV1c2VyOnBhc3N3b3Jk", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(1, result, "Valid route credentials accepted");
|
||||
|
||||
result = auth_check_route_basic_auth(&route, "Basic d3Jvbmd1c2VyOnBhc3N3b3Jk", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(0, result, "Wrong username rejected");
|
||||
|
||||
result = auth_check_route_basic_auth(&route, "Basic cm91dGV1c2VyOndyb25ncGFzcw==", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(0, result, "Wrong password rejected");
|
||||
|
||||
result = auth_check_route_basic_auth(&route, "Bearer token123", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(0, result, "Bearer auth rejected for route");
|
||||
|
||||
result = auth_check_route_basic_auth(&route, "Basic !!invalid!!", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(0, result, "Invalid base64 rejected for route");
|
||||
|
||||
result = auth_check_route_basic_auth(&route, "Basic bm9jb2xvbg==", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(0, result, "Base64 without colon rejected for route");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_auth_base64_edge_cases(void) {
|
||||
TEST_SUITE_BEGIN("Auth Base64 Edge Cases");
|
||||
|
||||
auth_init("admin", "pass");
|
||||
|
||||
char error[256];
|
||||
|
||||
int result = auth_check_basic_auth("Basic YWRtaW46cGFzcw==", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(1, result, "Standard base64 works");
|
||||
|
||||
result = auth_check_basic_auth("Basic YTo=", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(0, result, "Short credentials rejected");
|
||||
|
||||
result = auth_check_basic_auth("Basic YTpi", error, sizeof(error));
|
||||
TEST_ASSERT_EQ(0, result, "Minimal base64 without padding");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_auth_error_messages(void) {
|
||||
TEST_SUITE_BEGIN("Auth Error Messages");
|
||||
|
||||
auth_init("admin", "secret");
|
||||
|
||||
char error[256];
|
||||
memset(error, 0, sizeof(error));
|
||||
|
||||
auth_check_basic_auth(NULL, error, sizeof(error));
|
||||
TEST_ASSERT(strlen(error) > 0, "Error message set for NULL header");
|
||||
|
||||
memset(error, 0, sizeof(error));
|
||||
auth_check_basic_auth("Invalid", error, sizeof(error));
|
||||
TEST_ASSERT(strlen(error) > 0, "Error message set for invalid method");
|
||||
|
||||
auth_check_basic_auth(NULL, NULL, 0);
|
||||
TEST_ASSERT(1, "NULL error buffer handled");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_auth_disabled_passthrough(void) {
|
||||
TEST_SUITE_BEGIN("Auth Disabled Passthrough");
|
||||
|
||||
@@ -175,8 +103,5 @@ void run_auth_tests(void) {
|
||||
test_auth_check_credentials();
|
||||
test_auth_check_basic_auth();
|
||||
test_auth_route_basic_auth();
|
||||
test_auth_route_full_flow();
|
||||
test_auth_base64_edge_cases();
|
||||
test_auth_error_messages();
|
||||
test_auth_disabled_passthrough();
|
||||
}
|
||||
|
||||
Executable → Regular
-123
@@ -149,123 +149,6 @@ void test_buffer_multiple_operations(void) {
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_buffer_null_safety(void) {
|
||||
TEST_SUITE_BEGIN("Buffer NULL Safety");
|
||||
|
||||
TEST_ASSERT_EQ(-1, buffer_init(NULL, 1024), "NULL buffer init returns -1");
|
||||
|
||||
buffer_free(NULL);
|
||||
TEST_ASSERT(1, "NULL buffer free doesn't crash");
|
||||
|
||||
TEST_ASSERT_EQ(0, buffer_available_read(NULL), "NULL buffer read returns 0");
|
||||
TEST_ASSERT_EQ(0, buffer_available_write(NULL), "NULL buffer write returns 0");
|
||||
|
||||
TEST_ASSERT_EQ(-1, buffer_ensure_capacity(NULL, 1024), "NULL buffer ensure capacity returns -1");
|
||||
|
||||
buffer_compact(NULL);
|
||||
TEST_ASSERT(1, "NULL buffer compact doesn't crash");
|
||||
|
||||
buffer_consume(NULL, 100);
|
||||
TEST_ASSERT(1, "NULL buffer consume doesn't crash");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_buffer_consume_overflow(void) {
|
||||
TEST_SUITE_BEGIN("Buffer Consume Overflow");
|
||||
|
||||
buffer_t buf;
|
||||
buffer_init(&buf, 64);
|
||||
|
||||
memcpy(buf.data, "TEST", 4);
|
||||
buf.tail = 4;
|
||||
|
||||
buffer_consume(&buf, 100);
|
||||
TEST_ASSERT_EQ(0, buf.head, "Head reset after over-consume");
|
||||
TEST_ASSERT_EQ(0, buf.tail, "Tail reset after over-consume");
|
||||
TEST_ASSERT_EQ(0, buffer_available_read(&buf), "No data after over-consume");
|
||||
|
||||
buffer_free(&buf);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_buffer_compact_edge_cases(void) {
|
||||
TEST_SUITE_BEGIN("Buffer Compact Edge Cases");
|
||||
|
||||
buffer_t buf;
|
||||
buffer_init(&buf, 64);
|
||||
|
||||
buffer_compact(&buf);
|
||||
TEST_ASSERT_EQ(0, buf.head, "Compact empty buffer - head is 0");
|
||||
TEST_ASSERT_EQ(0, buf.tail, "Compact empty buffer - tail is 0");
|
||||
|
||||
memcpy(buf.data, "TEST", 4);
|
||||
buf.tail = 4;
|
||||
buf.head = 0;
|
||||
buffer_compact(&buf);
|
||||
TEST_ASSERT_EQ(0, buf.head, "Compact with head=0 unchanged");
|
||||
|
||||
buffer_free(&buf);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_buffer_capacity_limits(void) {
|
||||
TEST_SUITE_BEGIN("Buffer Capacity Limits");
|
||||
|
||||
buffer_t buf;
|
||||
buffer_init(&buf, 64);
|
||||
|
||||
int result = buffer_ensure_capacity(&buf, MAX_BUFFER_SIZE + 1);
|
||||
TEST_ASSERT_EQ(-1, result, "Exceeding MAX_BUFFER_SIZE returns -1");
|
||||
|
||||
buffer_free(&buf);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_buffer_large_growth(void) {
|
||||
TEST_SUITE_BEGIN("Buffer Large Growth");
|
||||
|
||||
buffer_t buf;
|
||||
buffer_init(&buf, 64);
|
||||
|
||||
int result = buffer_ensure_capacity(&buf, 1024);
|
||||
TEST_ASSERT_EQ(0, result, "Grow to 1024 succeeds");
|
||||
TEST_ASSERT(buf.capacity >= 1024, "Capacity at least 1024");
|
||||
|
||||
result = buffer_ensure_capacity(&buf, 4096);
|
||||
TEST_ASSERT_EQ(0, result, "Grow to 4096 succeeds");
|
||||
TEST_ASSERT(buf.capacity >= 4096, "Capacity at least 4096");
|
||||
|
||||
result = buffer_ensure_capacity(&buf, 65536);
|
||||
TEST_ASSERT_EQ(0, result, "Grow to 65536 succeeds");
|
||||
TEST_ASSERT(buf.capacity >= 65536, "Capacity at least 65536");
|
||||
|
||||
buffer_free(&buf);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_buffer_near_max_capacity(void) {
|
||||
TEST_SUITE_BEGIN("Buffer Near Max Capacity");
|
||||
|
||||
buffer_t buf;
|
||||
buffer_init(&buf, 1024);
|
||||
|
||||
int result = buffer_ensure_capacity(&buf, MAX_BUFFER_SIZE - 1024);
|
||||
TEST_ASSERT_EQ(0, result, "Grow to near max succeeds");
|
||||
|
||||
result = buffer_ensure_capacity(&buf, MAX_BUFFER_SIZE);
|
||||
TEST_ASSERT_EQ(0, result, "Grow to exactly max succeeds");
|
||||
TEST_ASSERT(buf.capacity <= MAX_BUFFER_SIZE, "Capacity at max");
|
||||
|
||||
buffer_free(&buf);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_buffer_tests(void) {
|
||||
test_buffer_init();
|
||||
test_buffer_read_write();
|
||||
@@ -273,10 +156,4 @@ void run_buffer_tests(void) {
|
||||
test_buffer_compact();
|
||||
test_buffer_ensure_capacity();
|
||||
test_buffer_multiple_operations();
|
||||
test_buffer_null_safety();
|
||||
test_buffer_consume_overflow();
|
||||
test_buffer_compact_edge_cases();
|
||||
test_buffer_capacity_limits();
|
||||
test_buffer_large_growth();
|
||||
test_buffer_near_max_capacity();
|
||||
}
|
||||
|
||||
Executable → Regular
Executable → Regular
-2151
File diff suppressed because it is too large
Load Diff
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
-52
@@ -182,56 +182,6 @@ void test_http_malformed_requests(void) {
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_http_uri_normalization(void) {
|
||||
TEST_SUITE_BEGIN("HTTP URI Path Normalization");
|
||||
|
||||
char normalized[256];
|
||||
|
||||
http_normalize_uri_path("/rproxy/dashboard", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard", normalized, "Normal path unchanged");
|
||||
|
||||
http_normalize_uri_path("//rproxy/dashboard", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard", normalized, "Double slash at start collapsed");
|
||||
|
||||
http_normalize_uri_path("/rproxy//dashboard", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard", normalized, "Double slash in middle collapsed");
|
||||
|
||||
http_normalize_uri_path("/./rproxy/dashboard", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard", normalized, "Dot segment at start removed");
|
||||
|
||||
http_normalize_uri_path("/rproxy/./dashboard", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard", normalized, "Dot segment in middle removed");
|
||||
|
||||
http_normalize_uri_path("/foo/../rproxy/dashboard", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard", normalized, "Parent reference resolved");
|
||||
|
||||
http_normalize_uri_path("/rproxy/dashboard?foo=bar", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard?foo=bar", normalized, "Query string preserved");
|
||||
|
||||
http_normalize_uri_path("///rproxy///dashboard///", normalized, sizeof(normalized));
|
||||
TEST_ASSERT_STR_EQ("/rproxy/dashboard/", normalized, "Multiple slashes collapsed");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_http_internal_route_detection(void) {
|
||||
TEST_SUITE_BEGIN("HTTP Internal Route Detection");
|
||||
|
||||
TEST_ASSERT_EQ(1, http_uri_is_internal_route("/rproxy/dashboard"), "Normal dashboard path detected");
|
||||
TEST_ASSERT_EQ(1, http_uri_is_internal_route("/rproxy/api/stats"), "Normal stats path detected");
|
||||
TEST_ASSERT_EQ(1, http_uri_is_internal_route("//rproxy/dashboard"), "Double slash bypass detected");
|
||||
TEST_ASSERT_EQ(1, http_uri_is_internal_route("/./rproxy/dashboard"), "Dot segment bypass detected");
|
||||
TEST_ASSERT_EQ(1, http_uri_is_internal_route("/foo/../rproxy/dashboard"), "Parent reference bypass detected");
|
||||
TEST_ASSERT_EQ(1, http_uri_is_internal_route("/rproxy//dashboard"), "Slash in path bypass detected");
|
||||
TEST_ASSERT_EQ(1, http_uri_is_internal_route("/rproxy/./api/stats"), "Dot in internal path detected");
|
||||
TEST_ASSERT_EQ(0, http_uri_is_internal_route("/api/data"), "Non-internal path rejected");
|
||||
TEST_ASSERT_EQ(0, http_uri_is_internal_route("/"), "Root path rejected");
|
||||
TEST_ASSERT_EQ(0, http_uri_is_internal_route("/rproxynotreal"), "Similar prefix rejected");
|
||||
TEST_ASSERT_EQ(0, http_uri_is_internal_route(NULL), "NULL path rejected");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_http_tests(void) {
|
||||
test_http_parse_get_request();
|
||||
test_http_parse_post_request();
|
||||
@@ -243,6 +193,4 @@ void run_http_tests(void) {
|
||||
test_http_parse_host_with_port();
|
||||
test_http_is_request_start();
|
||||
test_http_malformed_requests();
|
||||
test_http_uri_normalization();
|
||||
test_http_internal_route_detection();
|
||||
}
|
||||
|
||||
Executable → Regular
@@ -1,277 +0,0 @@
|
||||
#include "test_framework.h"
|
||||
#include "../src/logging.h"
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
void test_logging_debug_mode(void) {
|
||||
TEST_SUITE_BEGIN("Logging Debug Mode");
|
||||
|
||||
logging_set_debug(0);
|
||||
TEST_ASSERT_EQ(0, logging_get_debug(), "Debug mode disabled");
|
||||
|
||||
logging_set_debug(1);
|
||||
TEST_ASSERT_EQ(1, logging_get_debug(), "Debug mode enabled");
|
||||
|
||||
logging_set_debug(0);
|
||||
TEST_ASSERT_EQ(0, logging_get_debug(), "Debug mode disabled again");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_set_file(void) {
|
||||
TEST_SUITE_BEGIN("Logging Set File");
|
||||
|
||||
char tmp_path[] = "/tmp/test_rproxy_log_XXXXXX";
|
||||
int fd = mkstemp(tmp_path);
|
||||
TEST_ASSERT(fd >= 0, "Temp file created");
|
||||
close(fd);
|
||||
|
||||
int result = logging_set_file(tmp_path);
|
||||
TEST_ASSERT_EQ(0, result, "Set log file succeeds");
|
||||
|
||||
log_info("Test log message");
|
||||
|
||||
struct stat st;
|
||||
stat(tmp_path, &st);
|
||||
TEST_ASSERT(st.st_size > 0, "Log file has content");
|
||||
|
||||
logging_cleanup();
|
||||
unlink(tmp_path);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_set_file_null(void) {
|
||||
TEST_SUITE_BEGIN("Logging Set File NULL");
|
||||
|
||||
int result = logging_set_file(NULL);
|
||||
TEST_ASSERT_EQ(0, result, "NULL path returns to stdout");
|
||||
|
||||
log_info("Test message to stdout");
|
||||
TEST_ASSERT(1, "Logging to stdout works");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_set_file_invalid(void) {
|
||||
TEST_SUITE_BEGIN("Logging Set File Invalid Path");
|
||||
|
||||
int result = logging_set_file("/nonexistent/directory/log.txt");
|
||||
TEST_ASSERT_EQ(-1, result, "Invalid path returns -1");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_log_functions(void) {
|
||||
TEST_SUITE_BEGIN("Logging Log Functions");
|
||||
|
||||
char tmp_path[] = "/tmp/test_rproxy_log_XXXXXX";
|
||||
int fd = mkstemp(tmp_path);
|
||||
close(fd);
|
||||
|
||||
logging_set_file(tmp_path);
|
||||
|
||||
log_info("Info message: %d", 42);
|
||||
log_error("Error message: %s", "test error");
|
||||
|
||||
logging_set_debug(1);
|
||||
log_debug("Debug message: %s", "debug info");
|
||||
logging_set_debug(0);
|
||||
|
||||
log_debug("This should not appear");
|
||||
|
||||
struct stat st;
|
||||
stat(tmp_path, &st);
|
||||
TEST_ASSERT(st.st_size > 0, "Log messages written");
|
||||
|
||||
FILE *f = fopen(tmp_path, "r");
|
||||
char content[4096] = {0};
|
||||
if (f) {
|
||||
size_t bytes_read = fread(content, 1, sizeof(content) - 1, f);
|
||||
(void)bytes_read;
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
TEST_ASSERT(strstr(content, "INFO") != NULL, "INFO level present");
|
||||
TEST_ASSERT(strstr(content, "ERROR") != NULL, "ERROR level present");
|
||||
TEST_ASSERT(strstr(content, "DEBUG") != NULL, "DEBUG level present");
|
||||
TEST_ASSERT(strstr(content, "42") != NULL, "Info param present");
|
||||
TEST_ASSERT(strstr(content, "test error") != NULL, "Error param present");
|
||||
|
||||
logging_cleanup();
|
||||
unlink(tmp_path);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_cleanup(void) {
|
||||
TEST_SUITE_BEGIN("Logging Cleanup");
|
||||
|
||||
char tmp_path[] = "/tmp/test_rproxy_log_XXXXXX";
|
||||
int fd = mkstemp(tmp_path);
|
||||
close(fd);
|
||||
|
||||
logging_set_file(tmp_path);
|
||||
log_info("Before cleanup");
|
||||
|
||||
logging_cleanup();
|
||||
|
||||
log_info("After cleanup to stdout");
|
||||
TEST_ASSERT(1, "Cleanup completed and logging works");
|
||||
|
||||
unlink(tmp_path);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_multiple_files(void) {
|
||||
TEST_SUITE_BEGIN("Logging Multiple File Switches");
|
||||
|
||||
char tmp1[] = "/tmp/test_rproxy_log1_XXXXXX";
|
||||
char tmp2[] = "/tmp/test_rproxy_log2_XXXXXX";
|
||||
|
||||
int fd1 = mkstemp(tmp1);
|
||||
int fd2 = mkstemp(tmp2);
|
||||
close(fd1);
|
||||
close(fd2);
|
||||
|
||||
logging_set_file(tmp1);
|
||||
log_info("Message to file 1");
|
||||
|
||||
logging_set_file(tmp2);
|
||||
log_info("Message to file 2");
|
||||
|
||||
struct stat st1, st2;
|
||||
stat(tmp1, &st1);
|
||||
stat(tmp2, &st2);
|
||||
|
||||
TEST_ASSERT(st1.st_size > 0, "First file has content");
|
||||
TEST_ASSERT(st2.st_size > 0, "Second file has content");
|
||||
|
||||
logging_cleanup();
|
||||
unlink(tmp1);
|
||||
unlink(tmp2);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_error_with_errno(void) {
|
||||
TEST_SUITE_BEGIN("Logging Error With Errno");
|
||||
|
||||
char tmp_path[] = "/tmp/test_rproxy_log_XXXXXX";
|
||||
int fd = mkstemp(tmp_path);
|
||||
close(fd);
|
||||
|
||||
logging_set_file(tmp_path);
|
||||
|
||||
errno = ENOENT;
|
||||
log_error("File not found error");
|
||||
errno = 0;
|
||||
|
||||
log_error("Error without errno");
|
||||
|
||||
errno = EPERM;
|
||||
log_error("Permission denied: %s", "/test/file");
|
||||
errno = 0;
|
||||
|
||||
logging_cleanup();
|
||||
|
||||
FILE *f = fopen(tmp_path, "r");
|
||||
char content[4096] = {0};
|
||||
if (f) {
|
||||
size_t bytes_read = fread(content, 1, sizeof(content) - 1, f);
|
||||
(void)bytes_read;
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
TEST_ASSERT(strstr(content, "File not found") != NULL, "First error logged");
|
||||
TEST_ASSERT(strstr(content, "without errno") != NULL, "Second error logged");
|
||||
|
||||
unlink(tmp_path);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_debug_disabled(void) {
|
||||
TEST_SUITE_BEGIN("Logging Debug Disabled");
|
||||
|
||||
char tmp_path[] = "/tmp/test_rproxy_log_XXXXXX";
|
||||
int fd = mkstemp(tmp_path);
|
||||
close(fd);
|
||||
|
||||
logging_set_file(tmp_path);
|
||||
logging_set_debug(0);
|
||||
|
||||
log_debug("This should not appear");
|
||||
log_info("This should appear");
|
||||
|
||||
struct stat st;
|
||||
stat(tmp_path, &st);
|
||||
TEST_ASSERT(st.st_size > 0, "File has some content");
|
||||
|
||||
FILE *f = fopen(tmp_path, "r");
|
||||
char content[4096] = {0};
|
||||
if (f) {
|
||||
size_t bytes_read = fread(content, 1, sizeof(content) - 1, f);
|
||||
(void)bytes_read;
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
TEST_ASSERT(strstr(content, "should appear") != NULL, "Info message present");
|
||||
|
||||
logging_cleanup();
|
||||
unlink(tmp_path);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_logging_format_strings(void) {
|
||||
TEST_SUITE_BEGIN("Logging Format Strings");
|
||||
|
||||
char tmp_path[] = "/tmp/test_rproxy_log_XXXXXX";
|
||||
int fd = mkstemp(tmp_path);
|
||||
close(fd);
|
||||
|
||||
logging_set_file(tmp_path);
|
||||
|
||||
log_info("Int: %d, String: %s, Float: %.2f", 42, "test", 3.14);
|
||||
log_error("Code: %d, Msg: %s", 500, "Internal error");
|
||||
|
||||
logging_set_debug(1);
|
||||
log_debug("Debug: %s %d", "value", 123);
|
||||
logging_set_debug(0);
|
||||
|
||||
logging_cleanup();
|
||||
|
||||
FILE *f = fopen(tmp_path, "r");
|
||||
char content[4096] = {0};
|
||||
if (f) {
|
||||
size_t bytes_read = fread(content, 1, sizeof(content) - 1, f);
|
||||
(void)bytes_read;
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
TEST_ASSERT(strstr(content, "42") != NULL, "Int formatted");
|
||||
TEST_ASSERT(strstr(content, "test") != NULL, "String formatted");
|
||||
TEST_ASSERT(strstr(content, "500") != NULL, "Error code formatted");
|
||||
|
||||
unlink(tmp_path);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_logging_tests(void) {
|
||||
test_logging_debug_mode();
|
||||
test_logging_set_file();
|
||||
test_logging_set_file_null();
|
||||
test_logging_set_file_invalid();
|
||||
test_logging_log_functions();
|
||||
test_logging_cleanup();
|
||||
test_logging_multiple_files();
|
||||
test_logging_error_with_errno();
|
||||
test_logging_debug_disabled();
|
||||
test_logging_format_strings();
|
||||
}
|
||||
Executable → Regular
-2
@@ -30,7 +30,6 @@ extern void run_dashboard_tests(void);
|
||||
extern void run_health_check_tests(void);
|
||||
extern void run_ssl_handler_tests(void);
|
||||
extern void run_connection_tests(void);
|
||||
extern void run_logging_tests(void);
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
@@ -55,7 +54,6 @@ int main(int argc, char *argv[]) {
|
||||
run_health_check_tests();
|
||||
run_ssl_handler_tests();
|
||||
run_connection_tests();
|
||||
run_logging_tests();
|
||||
|
||||
test_summary();
|
||||
|
||||
|
||||
Executable → Regular
-89
@@ -230,91 +230,6 @@ void test_monitor_update(void) {
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_record_request_end(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Record Request End");
|
||||
|
||||
monitor_init(NULL);
|
||||
|
||||
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("timing.test.com");
|
||||
TEST_ASSERT(stats != NULL, "Stats created");
|
||||
|
||||
double start_time = 1000.0;
|
||||
monitor_record_request_end(stats, start_time);
|
||||
TEST_ASSERT(stats->avg_request_time_ms >= 0, "Avg request time recorded");
|
||||
|
||||
monitor_record_request_end(stats, start_time);
|
||||
monitor_record_request_end(stats, start_time);
|
||||
TEST_ASSERT(1, "Multiple request end calls work");
|
||||
|
||||
monitor_record_request_end(NULL, 0);
|
||||
TEST_ASSERT(1, "NULL stats doesn't crash");
|
||||
|
||||
monitor_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_deque_overflow(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Deque Overflow Behavior");
|
||||
|
||||
history_deque_t dq;
|
||||
history_deque_init(&dq, 3);
|
||||
|
||||
history_deque_push(&dq, 1.0, 10.0);
|
||||
history_deque_push(&dq, 2.0, 20.0);
|
||||
history_deque_push(&dq, 3.0, 30.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count is at capacity");
|
||||
|
||||
history_deque_push(&dq, 4.0, 40.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count stays at capacity");
|
||||
|
||||
history_deque_push(&dq, 5.0, 50.0);
|
||||
history_deque_push(&dq, 6.0, 60.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count still at capacity after multiple overflows");
|
||||
|
||||
free(dq.points);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_network_deque_overflow(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Network Deque Overflow");
|
||||
|
||||
network_history_deque_t dq;
|
||||
network_history_deque_init(&dq, 3);
|
||||
|
||||
network_history_deque_push(&dq, 1.0, 100.0, 50.0);
|
||||
network_history_deque_push(&dq, 2.0, 200.0, 100.0);
|
||||
network_history_deque_push(&dq, 3.0, 300.0, 150.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count is at capacity");
|
||||
|
||||
network_history_deque_push(&dq, 4.0, 400.0, 200.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count stays at capacity");
|
||||
|
||||
free(dq.points);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_disk_deque_overflow(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Disk Deque Overflow");
|
||||
|
||||
disk_history_deque_t dq;
|
||||
disk_history_deque_init(&dq, 3);
|
||||
|
||||
disk_history_deque_push(&dq, 1.0, 10.0, 5.0);
|
||||
disk_history_deque_push(&dq, 2.0, 20.0, 10.0);
|
||||
disk_history_deque_push(&dq, 3.0, 30.0, 15.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count is at capacity");
|
||||
|
||||
disk_history_deque_push(&dq, 4.0, 40.0, 20.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count stays at capacity");
|
||||
|
||||
free(dq.points);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_monitor_tests(void) {
|
||||
test_history_deque_init();
|
||||
test_history_deque_push();
|
||||
@@ -326,8 +241,4 @@ void run_monitor_tests(void) {
|
||||
test_monitor_record_request();
|
||||
test_monitor_record_bytes();
|
||||
test_monitor_update();
|
||||
test_monitor_record_request_end();
|
||||
test_monitor_deque_overflow();
|
||||
test_monitor_network_deque_overflow();
|
||||
test_monitor_disk_deque_overflow();
|
||||
}
|
||||
|
||||
Executable → Regular
-91
@@ -224,93 +224,6 @@ void test_patch_apply_block_rule(void) {
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_patch_apply_small_output_buffer(void) {
|
||||
TEST_SUITE_BEGIN("Patch Apply Small Output Buffer");
|
||||
|
||||
patch_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.rule_count = 1;
|
||||
strcpy(config.rules[0].key, "x");
|
||||
config.rules[0].key_len = 1;
|
||||
strcpy(config.rules[0].value, "longer");
|
||||
config.rules[0].value_len = 6;
|
||||
config.rules[0].is_null = 0;
|
||||
|
||||
const char *input = "x x x x x x x x x x";
|
||||
char output[10];
|
||||
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
|
||||
|
||||
TEST_ASSERT_EQ(0, result.should_block, "Small buffer does not block");
|
||||
TEST_ASSERT(result.output_len <= sizeof(output), "Output truncated to buffer size");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_patch_apply_null_input(void) {
|
||||
TEST_SUITE_BEGIN("Patch Apply NULL Input");
|
||||
|
||||
patch_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.rule_count = 1;
|
||||
strcpy(config.rules[0].key, "test");
|
||||
config.rules[0].key_len = 4;
|
||||
strcpy(config.rules[0].value, "demo");
|
||||
config.rules[0].value_len = 4;
|
||||
|
||||
char output[256];
|
||||
patch_result_t result = patch_apply(&config, NULL, 0, output, sizeof(output));
|
||||
TEST_ASSERT_EQ(0, result.should_block, "NULL input does not block");
|
||||
|
||||
result = patch_apply(NULL, "test", 4, output, sizeof(output));
|
||||
TEST_ASSERT_EQ(0, result.should_block, "NULL config does not block");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_patch_check_block_edge_cases(void) {
|
||||
TEST_SUITE_BEGIN("Patch Check Block Edge Cases");
|
||||
|
||||
patch_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.rule_count = 1;
|
||||
strcpy(config.rules[0].key, "verylongkeythatwontmatch");
|
||||
config.rules[0].key_len = strlen("verylongkeythatwontmatch");
|
||||
config.rules[0].is_null = 1;
|
||||
|
||||
const char *short_data = "hi";
|
||||
TEST_ASSERT_EQ(0, patch_check_for_block(&config, short_data, strlen(short_data)),
|
||||
"Key longer than data doesn't match");
|
||||
|
||||
config.rules[0].key_len = 0;
|
||||
TEST_ASSERT_EQ(0, patch_check_for_block(&config, "any data", 8),
|
||||
"Zero length key doesn't match");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_patch_apply_only_block_rules(void) {
|
||||
TEST_SUITE_BEGIN("Patch Apply Only Block Rules");
|
||||
|
||||
patch_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.rule_count = 2;
|
||||
strcpy(config.rules[0].key, "bad1");
|
||||
config.rules[0].key_len = 4;
|
||||
config.rules[0].is_null = 1;
|
||||
strcpy(config.rules[1].key, "bad2");
|
||||
config.rules[1].key_len = 4;
|
||||
config.rules[1].is_null = 1;
|
||||
|
||||
const char *input = "good content here";
|
||||
char output[256];
|
||||
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
|
||||
|
||||
TEST_ASSERT_EQ(0, result.should_block, "No block when content is clean");
|
||||
TEST_ASSERT_EQ(strlen(input), result.output_len, "Output unchanged with only block rules");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_patch_tests(void) {
|
||||
test_patch_has_rules();
|
||||
test_patch_check_for_block();
|
||||
@@ -321,8 +234,4 @@ void run_patch_tests(void) {
|
||||
test_patch_apply_no_match();
|
||||
test_patch_apply_empty_config();
|
||||
test_patch_apply_block_rule();
|
||||
test_patch_apply_small_output_buffer();
|
||||
test_patch_apply_null_input();
|
||||
test_patch_check_block_edge_cases();
|
||||
test_patch_apply_only_block_rules();
|
||||
}
|
||||
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
-83
@@ -191,85 +191,6 @@ void test_ssl_reinit(void) {
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_handshake_already_done(void) {
|
||||
TEST_SUITE_BEGIN("SSL Handshake Already Done");
|
||||
|
||||
connection_t conn;
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
conn.ssl_handshake_done = 1;
|
||||
|
||||
ssl_set_verify(0);
|
||||
ssl_init();
|
||||
conn.ssl = SSL_new(ssl_ctx);
|
||||
TEST_ASSERT(conn.ssl != NULL, "SSL object created");
|
||||
|
||||
int result = ssl_do_handshake(&conn);
|
||||
TEST_ASSERT_EQ(1, result, "Already done returns 1");
|
||||
|
||||
SSL_free(conn.ssl);
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_read_no_handshake(void) {
|
||||
TEST_SUITE_BEGIN("SSL Read Without Handshake");
|
||||
|
||||
connection_t conn;
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
conn.ssl_handshake_done = 0;
|
||||
|
||||
ssl_set_verify(0);
|
||||
ssl_init();
|
||||
conn.ssl = SSL_new(ssl_ctx);
|
||||
TEST_ASSERT(conn.ssl != NULL, "SSL object created");
|
||||
|
||||
char buf[100];
|
||||
int result = ssl_read(&conn, buf, sizeof(buf));
|
||||
TEST_ASSERT_EQ(-1, result, "Read without handshake returns -1");
|
||||
|
||||
SSL_free(conn.ssl);
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_write_no_handshake(void) {
|
||||
TEST_SUITE_BEGIN("SSL Write Without Handshake");
|
||||
|
||||
connection_t conn;
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
conn.ssl_handshake_done = 0;
|
||||
|
||||
ssl_set_verify(0);
|
||||
ssl_init();
|
||||
conn.ssl = SSL_new(ssl_ctx);
|
||||
TEST_ASSERT(conn.ssl != NULL, "SSL object created");
|
||||
|
||||
const char *buf = "test";
|
||||
int result = ssl_write(&conn, buf, strlen(buf));
|
||||
TEST_ASSERT_EQ(-1, result, "Write without handshake returns -1");
|
||||
|
||||
SSL_free(conn.ssl);
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_verify_with_ca(void) {
|
||||
TEST_SUITE_BEGIN("SSL Verify With CA Settings");
|
||||
|
||||
ssl_set_verify(1);
|
||||
ssl_set_ca_file("/etc/ssl/certs/ca-certificates.crt");
|
||||
ssl_set_ca_path("/etc/ssl/certs");
|
||||
ssl_init();
|
||||
TEST_ASSERT(ssl_ctx != NULL, "Context created with CA settings");
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_ssl_handler_tests(void) {
|
||||
test_ssl_init_cleanup();
|
||||
test_ssl_multiple_init();
|
||||
@@ -281,8 +202,4 @@ void run_ssl_handler_tests(void) {
|
||||
test_ssl_handshake_null();
|
||||
test_ssl_read_write_null();
|
||||
test_ssl_reinit();
|
||||
test_ssl_handshake_already_done();
|
||||
test_ssl_read_no_handshake();
|
||||
test_ssl_write_no_handshake();
|
||||
test_ssl_verify_with_ca();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user