Compare commits
30
Commits
main
..
e5ae782433
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
-3
@@ -10,6 +10,3 @@
|
||||
build
|
||||
__pycache__/
|
||||
rproxy
|
||||
CLAUDE.md
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
-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,10 +1,7 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -Werror -O3 -march=native -flto=auto -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
|
||||
CFLAGS = -Wall -Wextra -O2 -g -D_GNU_SOURCE
|
||||
CFLAGS_COV = -Wall -Wextra -g -D_GNU_SOURCE --coverage -fprofile-arcs -ftest-coverage
|
||||
LDFLAGS = -lssl -lcrypto -lsqlite3 -lm -lpthread
|
||||
LDFLAGS_COV = -lssl -lcrypto -lsqlite3 -lm -lpthread --coverage
|
||||
|
||||
SRC_DIR = src
|
||||
@@ -15,28 +12,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)))
|
||||
@@ -52,51 +36,32 @@ TEST_SOURCES = $(TESTS_DIR)/test_main.c \
|
||||
$(TESTS_DIR)/test_http_helpers.c \
|
||||
$(TESTS_DIR)/test_patch.c \
|
||||
$(TESTS_DIR)/test_auth.c \
|
||||
$(TESTS_DIR)/test_rate_limit.c \
|
||||
$(TESTS_DIR)/test_monitor.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_rate_limit.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)))
|
||||
|
||||
TEST_TARGET = rproxy_test
|
||||
|
||||
MIN_COVERAGE = 60
|
||||
COVERAGE_MODULES = auth.c buffer.c config.c http.c logging.c patch.c rate_limit.c monitor.c dashboard.c health_check.c ssl_handler.c connection.c
|
||||
MIN_COVERAGE = 70
|
||||
COVERAGE_MODULES = auth.c buffer.c config.c http.c logging.c patch.c rate_limit.c
|
||||
|
||||
.PHONY: all clean test legacy run coverage coverage-html valgrind
|
||||
.PHONY: all clean test legacy run coverage coverage-html
|
||||
|
||||
all: $(BUILD_DIR) $(TARGET)
|
||||
|
||||
@@ -106,13 +71,76 @@ $(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 $@
|
||||
|
||||
$(TEST_TARGET): $(BUILD_DIR) $(TEST_OBJECTS) $(TEST_LIB_OBJECTS)
|
||||
@@ -129,14 +157,29 @@ 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) -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 ""
|
||||
@@ -190,36 +233,5 @@ coverage-html: coverage
|
||||
genhtml coverage_report/coverage.info --output-directory coverage_report 2>/dev/null || echo "Install lcov for HTML reports: sudo apt install lcov"
|
||||
@echo "Coverage report generated in coverage_report/index.html"
|
||||
|
||||
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) $(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=$$?; \
|
||||
echo ""; \
|
||||
echo "=== Valgrind Summary ==="; \
|
||||
grep -E "(definitely|indirectly) lost:" valgrind.log || true; \
|
||||
grep "All heap blocks were freed" valgrind.log || true; \
|
||||
grep "ERROR SUMMARY:" valgrind.log || true; \
|
||||
if [ $$VALGRIND_EXIT -ne 0 ]; then \
|
||||
echo "FAILED: Valgrind detected errors"; rm -f valgrind.log; exit 1; \
|
||||
fi; \
|
||||
if grep -q "definitely lost: [1-9]" valgrind.log; then \
|
||||
echo "FAILED: Definite memory leaks detected"; rm -f valgrind.log; exit 1; \
|
||||
fi; \
|
||||
if grep -q "indirectly lost: [1-9]" valgrind.log; then \
|
||||
echo "FAILED: Indirect memory leaks detected"; rm -f valgrind.log; exit 1; \
|
||||
fi; \
|
||||
rm -f valgrind.log
|
||||
@echo ""
|
||||
@echo "=== Valgrind Check Passed ==="
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(TARGET) $(TEST_TARGET) rproxy_legacy *.gcov coverage_report
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# rproxy
|
||||
|
||||
Author: retoor <retoor@molodetz.nl>
|
||||
|
||||
A high-performance reverse proxy server written in C. Routes HTTP and WebSocket requests to upstream services based on hostname, with support for SSL/TLS connections and real-time monitoring.
|
||||
rproxy is a high-performance reverse proxy server written in C. It routes HTTP and WebSocket requests to upstream services based on hostname, with support for SSL/TLS termination and connection pooling.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -16,8 +14,7 @@ A high-performance reverse proxy server written in C. Routes HTTP and WebSocket
|
||||
- Epoll-based event handling for high concurrency
|
||||
- Graceful shutdown with connection draining
|
||||
- Live configuration reload via SIGHUP
|
||||
- Per-route authentication (HTTP Basic Auth)
|
||||
- Dashboard authentication
|
||||
- Dashboard authentication (HTTP Basic Auth)
|
||||
- Rate limiting per client IP
|
||||
- Health checks for upstream servers
|
||||
- Automatic upstream connection retries
|
||||
@@ -30,7 +27,7 @@ A high-performance reverse proxy server written in C. Routes HTTP and WebSocket
|
||||
- OpenSSL (libssl, libcrypto)
|
||||
- SQLite3
|
||||
- pthreads
|
||||
- cJSON (bundled)
|
||||
- cJSON library
|
||||
|
||||
## Build
|
||||
|
||||
@@ -38,30 +35,7 @@ A high-performance reverse proxy server written in C. Routes HTTP and WebSocket
|
||||
make
|
||||
```
|
||||
|
||||
Compiles the source files in `src/` and produces the `rproxy` executable.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
make test # Run unit tests
|
||||
make coverage # Run tests with coverage report (minimum 69% 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
|
||||
```
|
||||
This compiles the source files in `src/` and produces the `rproxy` executable.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -77,11 +51,9 @@ Configuration is defined in `proxy_config.json`:
|
||||
"upstream_port": 5000,
|
||||
"use_ssl": false,
|
||||
"rewrite_host": true,
|
||||
"use_auth": true,
|
||||
"username": "admin",
|
||||
"password": "secret",
|
||||
"patch": {
|
||||
"old_string": "new_string",
|
||||
"secret_key": "[REDACTED]",
|
||||
"blocked_content": null
|
||||
}
|
||||
}
|
||||
@@ -89,23 +61,18 @@ Configuration is defined in `proxy_config.json`:
|
||||
}
|
||||
```
|
||||
|
||||
### Route Options
|
||||
|
||||
| Option | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `hostname` | string | Host header to match for routing |
|
||||
| `upstream_host` | string | Target server hostname or IP |
|
||||
| `upstream_port` | integer | Target server port (1-65535) |
|
||||
| `use_ssl` | boolean | Enable SSL/TLS for upstream connection |
|
||||
| `rewrite_host` | boolean | Rewrite Host header to upstream hostname |
|
||||
| `use_auth` | boolean | Enable HTTP Basic Auth for this route |
|
||||
| `username` | string | Authentication username |
|
||||
| `password` | string | Authentication password |
|
||||
| `patch` | object | Stream data patching rules |
|
||||
- `port`: Listening port for incoming connections
|
||||
- `reverse_proxy`: Array of routing rules
|
||||
- `hostname`: Host header to match for routing
|
||||
- `upstream_host`: Target server hostname/IP
|
||||
- `upstream_port`: Target server port
|
||||
- `use_ssl`: Enable SSL for upstream connection
|
||||
- `rewrite_host`: Rewrite Host header to upstream hostname
|
||||
- `patch`: Optional object for stream data patching (see below)
|
||||
|
||||
### Data Patching
|
||||
|
||||
The `patch` configuration allows rewriting or blocking content in HTTP streams. Patch rules are applied to textual content only. Binary content passes through unmodified.
|
||||
The `patch` configuration allows rewriting or blocking content in HTTP streams. Patch rules are applied to textual content only (text/*, application/json, application/xml, etc.). Binary content passes through unmodified.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -119,17 +86,19 @@ The `patch` configuration allows rewriting or blocking content in HTTP streams.
|
||||
|
||||
- **String replacement**: Each key-value pair defines a find-replace rule
|
||||
- **Content blocking**: Setting value to `null` blocks the entire response/request when the key is found
|
||||
- **Bidirectional**: Patches apply to both requests and responses
|
||||
- **Bidirectional**: Patches apply to both requests (client → upstream) and responses (upstream → client)
|
||||
|
||||
Blocked responses return `502 Bad Gateway`. Blocked requests return `403 Forbidden`.
|
||||
When content is blocked:
|
||||
- Blocked responses return `502 Bad Gateway` to the client
|
||||
- Blocked requests return `403 Forbidden` to the client
|
||||
|
||||
Supported textual content types:
|
||||
- `text/*`
|
||||
- `text/*` (text/html, text/plain, text/css, etc.)
|
||||
- `application/json`
|
||||
- `application/xml`
|
||||
- `application/javascript`
|
||||
- `application/x-www-form-urlencoded`
|
||||
- Content types with `+xml` or `+json` suffix
|
||||
- Any content type with `+xml` or `+json` suffix
|
||||
|
||||
## Environment Variables
|
||||
|
||||
@@ -152,23 +121,35 @@ Supported textual content types:
|
||||
|
||||
If no config file is specified, defaults to `proxy_config.json`.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Basic usage
|
||||
./rproxy
|
||||
|
||||
# With custom config
|
||||
./rproxy /etc/rproxy/config.json
|
||||
|
||||
# With debug logging
|
||||
DEBUG=1 ./rproxy
|
||||
|
||||
# With file logging
|
||||
LOG_FILE=/var/log/rproxy.log ./rproxy
|
||||
|
||||
# With rate limiting (100 requests/minute)
|
||||
RATE_LIMIT=100 ./rproxy
|
||||
|
||||
# With dashboard authentication
|
||||
DASHBOARD_USER=admin DASHBOARD_PASS=secret ./rproxy
|
||||
SSL_VERIFY=0 ./rproxy
|
||||
kill -HUP $(pidof rproxy) # Reload configuration
|
||||
|
||||
# Reload configuration
|
||||
kill -HUP $(pidof rproxy)
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `/rproxy/dashboard` | Web-based monitoring dashboard |
|
||||
| `/rproxy/api/stats` | JSON API for statistics |
|
||||
- Dashboard: `http://localhost:{port}/rproxy/dashboard`
|
||||
- API Stats: `http://localhost:{port}/rproxy/api/stats`
|
||||
|
||||
## Signals
|
||||
|
||||
@@ -180,22 +161,24 @@ kill -HUP $(pidof rproxy) # Reload configuration
|
||||
|
||||
## Architecture
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `main.c` | Entry point, event loop, signal handling |
|
||||
| `connection.c` | Connection management, epoll handling |
|
||||
| `http.c` | HTTP request/response parsing |
|
||||
| `ssl_handler.c` | SSL/TLS connection handling |
|
||||
| `monitor.c` | System and per-vhost statistics collection |
|
||||
| `dashboard.c` | Web dashboard generation |
|
||||
| `config.c` | JSON configuration parsing with hot-reload |
|
||||
| `buffer.c` | Circular buffer implementation |
|
||||
| `logging.c` | Logging utilities |
|
||||
| `rate_limit.c` | Per-IP rate limiting with sliding window |
|
||||
| `auth.c` | HTTP Basic Auth implementation |
|
||||
| `health_check.c` | Upstream health monitoring |
|
||||
| `patch.c` | Stream data patching engine |
|
||||
- **main.c**: Entry point, event loop, signal handling
|
||||
- **connection.c**: Connection management, epoll handling
|
||||
- **http.c**: HTTP request/response parsing
|
||||
- **ssl_handler.c**: SSL/TLS connection handling
|
||||
- **monitor.c**: System and per-vhost statistics collection
|
||||
- **dashboard.c**: Web dashboard generation
|
||||
- **config.c**: JSON configuration parsing
|
||||
- **buffer.c**: Circular buffer implementation
|
||||
- **logging.c**: Logging utilities
|
||||
- **rate_limit.c**: Per-IP rate limiting
|
||||
- **auth.c**: Dashboard authentication
|
||||
- **health_check.c**: Upstream health monitoring
|
||||
- **patch.c**: Stream data patching engine
|
||||
|
||||
## License
|
||||
## Testing
|
||||
|
||||
See LICENSE file for details.
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
Runs unit tests for core components.
|
||||
+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
|
||||
+377
-97
@@ -1,113 +1,261 @@
|
||||
// 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>
|
||||
#include <pthread.h>
|
||||
|
||||
static pthread_rwlock_t config_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
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;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long length = ftell(f);
|
||||
if (length < 0 || length > 1024*1024) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_config->route_count = valid_routes;
|
||||
return new_config;
|
||||
fseek(f, 0, SEEK_SET);
|
||||
char *buffer = malloc(length + 1);
|
||||
if (buffer) {
|
||||
size_t read_len = fread(buffer, 1, 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;
|
||||
}
|
||||
|
||||
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");
|
||||
free(json_string);
|
||||
return 0;
|
||||
}
|
||||
free(json_string);
|
||||
|
||||
app_config_t *new_config = config_create_from_json(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (!new_config) {
|
||||
if (!root) {
|
||||
fprintf(stderr, "JSON parse error: %s\n", cJSON_GetErrorPtr());
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(filename, &st) == 0) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
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);
|
||||
@@ -120,14 +268,14 @@ int config_load(const char *filename) {
|
||||
|
||||
void config_ref_inc(app_config_t *conf) {
|
||||
if (conf) {
|
||||
atomic_fetch_add(&conf->ref_count, 1);
|
||||
__sync_fetch_and_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) {
|
||||
if (__sync_sub_and_fetch(&conf->ref_count, 1) == 0) {
|
||||
log_debug("Freeing configuration with port %d", conf->port);
|
||||
if (conf->routes) {
|
||||
free(conf->routes);
|
||||
@@ -141,6 +289,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) {
|
||||
@@ -182,17 +338,22 @@ void config_create_default(const char *filename) {
|
||||
route_config_t *config_find_route(const char *hostname) {
|
||||
if (!hostname) return NULL;
|
||||
|
||||
pthread_rwlock_rdlock(&config_lock);
|
||||
app_config_t *current_config = config;
|
||||
if (!current_config) {
|
||||
pthread_rwlock_unlock(&config_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
route_config_t *result = 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];
|
||||
result = ¤t_config->routes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
pthread_rwlock_unlock(&config_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
int config_check_file_changed(const char *filename) {
|
||||
@@ -214,37 +375,156 @@ 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;
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_Parse(json_string);
|
||||
free(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);
|
||||
log_error("Hot-reload: JSON parse error: %s", cJSON_GetErrorPtr());
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
free(json_string);
|
||||
|
||||
app_config_t *new_config = config_create_from_json(root);
|
||||
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);
|
||||
|
||||
if (!new_config) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(filename, &st) == 0) {
|
||||
config_file_mtime = st.st_mtime;
|
||||
}
|
||||
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
}
|
||||
pthread_rwlock_wrlock(&config_lock);
|
||||
app_config_t *old_config = config;
|
||||
config = new_config;
|
||||
pthread_rwlock_unlock(&config_lock);
|
||||
|
||||
if (old_config) {
|
||||
old_config->next = stale_configs_head;
|
||||
stale_configs_head = old_config;
|
||||
config_ref_dec(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
|
||||
+814
-346
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
+24
-7
@@ -1,8 +1,6 @@
|
||||
#include "health_check.h"
|
||||
#include "logging.h"
|
||||
#include "config.h"
|
||||
#include "types.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
@@ -13,10 +11,11 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct {
|
||||
char hostname[HOSTNAME_MAX_LEN];
|
||||
char upstream_host[HOSTNAME_MAX_LEN];
|
||||
char hostname[256];
|
||||
char upstream_host[256];
|
||||
int upstream_port;
|
||||
int healthy;
|
||||
int consecutive_failures;
|
||||
@@ -25,9 +24,12 @@ typedef struct {
|
||||
|
||||
static upstream_health_t *health_states = NULL;
|
||||
static int health_state_count = 0;
|
||||
static pthread_mutex_t health_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int g_health_check_enabled = 0;
|
||||
|
||||
void health_check_init(void) {
|
||||
pthread_mutex_lock(&health_mutex);
|
||||
|
||||
if (health_states) {
|
||||
free(health_states);
|
||||
}
|
||||
@@ -35,18 +37,20 @@ void health_check_init(void) {
|
||||
health_state_count = config->route_count;
|
||||
if (health_state_count <= 0) {
|
||||
health_states = NULL;
|
||||
pthread_mutex_unlock(&health_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
health_states = calloc(health_state_count, sizeof(upstream_health_t));
|
||||
if (!health_states) {
|
||||
health_state_count = 0;
|
||||
pthread_mutex_unlock(&health_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < health_state_count; i++) {
|
||||
snprintf(health_states[i].hostname, sizeof(health_states[i].hostname), "%s", config->routes[i].hostname);
|
||||
snprintf(health_states[i].upstream_host, sizeof(health_states[i].upstream_host), "%s", config->routes[i].upstream_host);
|
||||
strncpy(health_states[i].hostname, config->routes[i].hostname, sizeof(health_states[i].hostname) - 1);
|
||||
strncpy(health_states[i].upstream_host, config->routes[i].upstream_host, sizeof(health_states[i].upstream_host) - 1);
|
||||
health_states[i].upstream_port = config->routes[i].upstream_port;
|
||||
health_states[i].healthy = 1;
|
||||
health_states[i].consecutive_failures = 0;
|
||||
@@ -55,15 +59,19 @@ void health_check_init(void) {
|
||||
|
||||
g_health_check_enabled = 1;
|
||||
log_info("Health check initialized for %d upstreams", health_state_count);
|
||||
|
||||
pthread_mutex_unlock(&health_mutex);
|
||||
}
|
||||
|
||||
void health_check_cleanup(void) {
|
||||
pthread_mutex_lock(&health_mutex);
|
||||
if (health_states) {
|
||||
free(health_states);
|
||||
health_states = NULL;
|
||||
}
|
||||
health_state_count = 0;
|
||||
g_health_check_enabled = 0;
|
||||
pthread_mutex_unlock(&health_mutex);
|
||||
}
|
||||
|
||||
static int check_tcp_connection(const char *host, int port, int timeout_ms) {
|
||||
@@ -122,6 +130,8 @@ static int check_tcp_connection(const char *host, int port, int timeout_ms) {
|
||||
void health_check_run(void) {
|
||||
if (!g_health_check_enabled) return;
|
||||
|
||||
pthread_mutex_lock(&health_mutex);
|
||||
|
||||
time_t now = time(NULL);
|
||||
|
||||
for (int i = 0; i < health_state_count; i++) {
|
||||
@@ -156,16 +166,23 @@ void health_check_run(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&health_mutex);
|
||||
}
|
||||
|
||||
int health_check_is_healthy(const char *hostname) {
|
||||
if (!g_health_check_enabled || !hostname) return 1;
|
||||
|
||||
pthread_mutex_lock(&health_mutex);
|
||||
|
||||
for (int i = 0; i < health_state_count; i++) {
|
||||
if (strcasecmp(health_states[i].hostname, hostname) == 0) {
|
||||
return health_states[i].healthy;
|
||||
int result = health_states[i].healthy;
|
||||
pthread_mutex_unlock(&health_mutex);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&health_mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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
-53
@@ -5,15 +5,9 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LOG_MAX_SIZE (10 * 1024 * 1024)
|
||||
#define LOG_MAX_ROTATIONS 5
|
||||
|
||||
static int g_debug_mode = 0;
|
||||
static FILE *g_log_file = NULL;
|
||||
static char g_log_path[512] = "";
|
||||
static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void logging_set_debug(int enabled) {
|
||||
@@ -24,59 +18,15 @@ int logging_get_debug(void) {
|
||||
return g_debug_mode;
|
||||
}
|
||||
|
||||
static void rotate_log_file(void) {
|
||||
if (g_log_path[0] == '\0') return;
|
||||
|
||||
if (g_log_file && g_log_file != stdout && g_log_file != stderr) {
|
||||
fclose(g_log_file);
|
||||
g_log_file = NULL;
|
||||
}
|
||||
|
||||
char old_path[536], new_path[536];
|
||||
|
||||
snprintf(old_path, sizeof(old_path), "%s.%d", g_log_path, LOG_MAX_ROTATIONS);
|
||||
unlink(old_path);
|
||||
|
||||
for (int i = LOG_MAX_ROTATIONS - 1; i >= 1; i--) {
|
||||
snprintf(old_path, sizeof(old_path), "%s.%d", g_log_path, i);
|
||||
snprintf(new_path, sizeof(new_path), "%s.%d", g_log_path, i + 1);
|
||||
rename(old_path, new_path);
|
||||
}
|
||||
|
||||
snprintf(new_path, sizeof(new_path), "%s.1", g_log_path);
|
||||
rename(g_log_path, new_path);
|
||||
|
||||
g_log_file = fopen(g_log_path, "a");
|
||||
if (!g_log_file) {
|
||||
g_log_file = stdout;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_rotation(void) {
|
||||
if (!g_log_file || g_log_file == stdout || g_log_file == stderr) return;
|
||||
if (g_log_path[0] == '\0') return;
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fileno(g_log_file), &st) == 0) {
|
||||
if (st.st_size >= LOG_MAX_SIZE) {
|
||||
rotate_log_file();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int logging_set_file(const char *path) {
|
||||
pthread_mutex_lock(&log_mutex);
|
||||
if (g_log_file && g_log_file != stdout && g_log_file != stderr) {
|
||||
fclose(g_log_file);
|
||||
}
|
||||
g_log_path[0] = '\0';
|
||||
if (path) {
|
||||
strncpy(g_log_path, path, sizeof(g_log_path) - 1);
|
||||
g_log_path[sizeof(g_log_path) - 1] = '\0';
|
||||
g_log_file = fopen(path, "a");
|
||||
if (!g_log_file) {
|
||||
g_log_file = stdout;
|
||||
g_log_path[0] = '\0';
|
||||
pthread_mutex_unlock(&log_mutex);
|
||||
return -1;
|
||||
}
|
||||
@@ -93,13 +43,11 @@ void logging_cleanup(void) {
|
||||
fclose(g_log_file);
|
||||
}
|
||||
g_log_file = NULL;
|
||||
g_log_path[0] = '\0';
|
||||
pthread_mutex_unlock(&log_mutex);
|
||||
}
|
||||
|
||||
static void log_message(const char *level, const char *format, va_list args) {
|
||||
pthread_mutex_lock(&log_mutex);
|
||||
check_rotation();
|
||||
|
||||
FILE *out = g_log_file ? g_log_file : stdout;
|
||||
time_t now;
|
||||
@@ -125,7 +73,6 @@ void log_error(const char *format, ...) {
|
||||
va_end(args);
|
||||
|
||||
pthread_mutex_lock(&log_mutex);
|
||||
check_rotation();
|
||||
|
||||
FILE *out = g_log_file ? g_log_file : stderr;
|
||||
time_t now;
|
||||
|
||||
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();
|
||||
|
||||
+397
-330
@@ -1,31 +1,134 @@
|
||||
// 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>
|
||||
#include <pthread.h>
|
||||
|
||||
system_monitor_t monitor = {0};
|
||||
system_monitor_t monitor;
|
||||
static pthread_mutex_t vhost_stats_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
static void init_db(void) {
|
||||
if (!monitor.db) return;
|
||||
|
||||
char *err_msg = 0;
|
||||
const char *sql_create_table =
|
||||
"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_index =
|
||||
"CREATE INDEX IF NOT EXISTS idx_vhost_timestamp ON vhost_stats(vhost, timestamp);";
|
||||
|
||||
if (sqlite3_exec(monitor.db, sql_create_table, 0, 0, &err_msg) != SQLITE_OK ||
|
||||
sqlite3_exec(monitor.db, sql_create_index, 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, avg_request_time_ms "
|
||||
"FROM vhost_stats v1 WHERE timestamp = ("
|
||||
" SELECT MAX(timestamp) FROM vhost_stats v2 WHERE v2.vhost = v1.vhost"
|
||||
")";
|
||||
|
||||
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);
|
||||
stats->avg_request_time_ms = sqlite3_column_double(res, 6);
|
||||
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,354 +139,318 @@ 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);
|
||||
if (monitor.memory_history.points) free(monitor.memory_history.points);
|
||||
if (monitor.network_history.points) free(monitor.network_history.points);
|
||||
if (monitor.disk_history.points) free(monitor.disk_history.points);
|
||||
if (monitor.throughput_history.points) free(monitor.throughput_history.points);
|
||||
if (monitor.load1_history.points) free(monitor.load1_history.points);
|
||||
if (monitor.load5_history.points) free(monitor.load5_history.points);
|
||||
if (monitor.load15_history.points) free(monitor.load15_history.points);
|
||||
}
|
||||
|
||||
vhost_stats_t *monitor_get_or_create_vhost_stats(const char *vhost_name) {
|
||||
if (!vhost_name || vhost_name[0] == '\0') return NULL;
|
||||
static void save_stats_to_db(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;
|
||||
sqlite3_stmt *stmt;
|
||||
const char *sql =
|
||||
"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, -1, &stmt, NULL) != SQLITE_OK) 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, 1, s->vhost_name, -1, SQLITE_STATIC);
|
||||
sqlite3_bind_double(stmt, 2, current_time);
|
||||
sqlite3_bind_int64(stmt, 3, s->http_requests);
|
||||
sqlite3_bind_int64(stmt, 4, s->websocket_requests);
|
||||
sqlite3_bind_int64(stmt, 5, s->total_requests);
|
||||
sqlite3_bind_int64(stmt, 6, s->bytes_sent);
|
||||
sqlite3_bind_int64(stmt, 7, s->bytes_recv);
|
||||
sqlite3_bind_double(stmt, 8, s->avg_request_time_ms);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
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);
|
||||
pthread_mutex_lock(&vhost_stats_mutex);
|
||||
|
||||
stats->next = monitor.vhost_stats_head;
|
||||
monitor.vhost_stats_head = stats;
|
||||
for (vhost_stats_t *curr = monitor.vhost_stats_head; curr; curr = curr->next) {
|
||||
if (strcmp(curr->vhost_name, vhost_name) == 0) {
|
||||
pthread_mutex_unlock(&vhost_stats_mutex);
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
vhost_stats_t *new_stats = calloc(1, sizeof(vhost_stats_t));
|
||||
if (!new_stats) {
|
||||
pthread_mutex_unlock(&vhost_stats_mutex);
|
||||
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);
|
||||
new_stats->next = monitor.vhost_stats_head;
|
||||
monitor.vhost_stats_head = new_stats;
|
||||
|
||||
pthread_mutex_unlock(&vhost_stats_mutex);
|
||||
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++;
|
||||
__sync_fetch_and_add(&stats->websocket_requests, 1);
|
||||
} else {
|
||||
stats->http_requests++;
|
||||
__sync_fetch_and_add(&stats->http_requests, 1);
|
||||
}
|
||||
rate_tracker_increment(&stats->requests_per_second);
|
||||
rate_tracker_increment(&monitor.global_rps);
|
||||
__sync_fetch_and_add(&stats->total_requests, 1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
__sync_fetch_and_add(&stats->bytes_sent, sent);
|
||||
__sync_fetch_and_add(&stats->bytes_recv, recv);
|
||||
}
|
||||
|
||||
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
+16
-3
@@ -3,6 +3,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define MAX_RATE_LIMIT_ENTRIES 10000
|
||||
|
||||
@@ -14,6 +15,7 @@ typedef struct rate_limit_entry {
|
||||
} rate_limit_entry_t;
|
||||
|
||||
static rate_limit_entry_t *rate_limit_table[256];
|
||||
static pthread_mutex_t rate_limit_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int g_rate_limit_enabled = 0;
|
||||
static int g_requests_per_window = DEFAULT_RATE_LIMIT_REQUESTS;
|
||||
static int g_window_seconds = RATE_LIMIT_WINDOW_SECONDS;
|
||||
@@ -35,6 +37,7 @@ void rate_limit_init(int requests_per_window, int window_seconds) {
|
||||
}
|
||||
|
||||
void rate_limit_cleanup(void) {
|
||||
pthread_mutex_lock(&rate_limit_mutex);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
rate_limit_entry_t *entry = rate_limit_table[i];
|
||||
while (entry) {
|
||||
@@ -44,12 +47,14 @@ void rate_limit_cleanup(void) {
|
||||
}
|
||||
rate_limit_table[i] = NULL;
|
||||
}
|
||||
g_rate_limit_enabled = 0;
|
||||
pthread_mutex_unlock(&rate_limit_mutex);
|
||||
}
|
||||
|
||||
int rate_limit_check(const char *client_ip) {
|
||||
if (!g_rate_limit_enabled || !client_ip) return 1;
|
||||
|
||||
pthread_mutex_lock(&rate_limit_mutex);
|
||||
|
||||
time_t now = time(NULL);
|
||||
unsigned int bucket = hash_ip(client_ip);
|
||||
rate_limit_entry_t *entry = rate_limit_table[bucket];
|
||||
@@ -59,14 +64,17 @@ int rate_limit_check(const char *client_ip) {
|
||||
if (now - entry->window_start >= g_window_seconds) {
|
||||
entry->window_start = now;
|
||||
entry->request_count = 1;
|
||||
pthread_mutex_unlock(&rate_limit_mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
entry->request_count++;
|
||||
if (entry->request_count > g_requests_per_window) {
|
||||
pthread_mutex_unlock(&rate_limit_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&rate_limit_mutex);
|
||||
return 1;
|
||||
}
|
||||
entry = entry->next;
|
||||
@@ -74,8 +82,8 @@ 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;
|
||||
pthread_mutex_unlock(&rate_limit_mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
strncpy(new_entry->client_ip, client_ip, sizeof(new_entry->client_ip) - 1);
|
||||
@@ -84,12 +92,15 @@ int rate_limit_check(const char *client_ip) {
|
||||
new_entry->next = rate_limit_table[bucket];
|
||||
rate_limit_table[bucket] = new_entry;
|
||||
|
||||
pthread_mutex_unlock(&rate_limit_mutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void rate_limit_purge_expired(void) {
|
||||
if (!g_rate_limit_enabled) return;
|
||||
|
||||
pthread_mutex_lock(&rate_limit_mutex);
|
||||
|
||||
time_t now = time(NULL);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
rate_limit_entry_t *entry = rate_limit_table[i];
|
||||
@@ -110,4 +121,6 @@ void rate_limit_purge_expired(void) {
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&rate_limit_mutex);
|
||||
}
|
||||
|
||||
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
+3
-25
@@ -2,7 +2,6 @@
|
||||
#include "logging.h"
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -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) {
|
||||
@@ -94,8 +73,7 @@ void ssl_cleanup(void) {
|
||||
}
|
||||
|
||||
int ssl_do_handshake(connection_t *conn) {
|
||||
if (!conn || !conn->ssl) return -1;
|
||||
if (conn->ssl_handshake_done) return 1;
|
||||
if (!conn->ssl || conn->ssl_handshake_done) return 1;
|
||||
|
||||
int ret = SSL_do_handshake(conn->ssl);
|
||||
if (ret == 1) {
|
||||
@@ -114,7 +92,7 @@ int ssl_do_handshake(connection_t *conn) {
|
||||
}
|
||||
|
||||
int ssl_read(connection_t *conn, char *buf, size_t len) {
|
||||
if (!conn || !conn->ssl || !conn->ssl_handshake_done) return -1;
|
||||
if (!conn->ssl || !conn->ssl_handshake_done) return -1;
|
||||
|
||||
int bytes_read = SSL_read(conn->ssl, buf, len);
|
||||
if (bytes_read <= 0) {
|
||||
@@ -128,7 +106,7 @@ int ssl_read(connection_t *conn, char *buf, size_t len) {
|
||||
}
|
||||
|
||||
int ssl_write(connection_t *conn, const char *buf, size_t len) {
|
||||
if (!conn || !conn->ssl || !conn->ssl_handshake_done) return -1;
|
||||
if (!conn->ssl || !conn->ssl_handshake_done) return -1;
|
||||
|
||||
int written = SSL_write(conn->ssl, buf, len);
|
||||
if (written <= 0) {
|
||||
|
||||
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
-106
@@ -6,7 +6,6 @@
|
||||
#endif
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <sqlite3.h>
|
||||
@@ -32,12 +31,6 @@
|
||||
#define MAX_PATCH_RULES 64
|
||||
#define MAX_PATCH_KEY_SIZE 256
|
||||
#define MAX_PATCH_VALUE_SIZE 1024
|
||||
#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 +47,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,14 +76,12 @@ 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;
|
||||
buffer_t write_buf;
|
||||
SSL *ssl;
|
||||
int ssl_handshake_done;
|
||||
time_t ssl_handshake_start;
|
||||
http_request_t request;
|
||||
double request_start_time;
|
||||
time_t last_activity;
|
||||
@@ -143,11 +95,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 +188,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 +210,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
File diff suppressed because it is too large
Load Diff
@@ -1,273 +0,0 @@
|
||||
#include "test_framework.h"
|
||||
#include "../src/types.h"
|
||||
#include "../src/dashboard.h"
|
||||
#include "../src/monitor.h"
|
||||
#include "../src/buffer.h"
|
||||
#include "../src/connection.h"
|
||||
#include "../src/auth.h"
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
extern connection_t connections[MAX_FDS];
|
||||
extern int epoll_fd;
|
||||
|
||||
static connection_t* create_test_connection(void) {
|
||||
static int test_fd = 100;
|
||||
int fd = test_fd++;
|
||||
|
||||
connection_t *conn = &connections[fd];
|
||||
memset(conn, 0, sizeof(connection_t));
|
||||
conn->fd = fd;
|
||||
conn->type = CONN_TYPE_CLIENT;
|
||||
conn->state = CLIENT_STATE_SERVING_INTERNAL;
|
||||
buffer_init(&conn->read_buf, 4096);
|
||||
buffer_init(&conn->write_buf, 65536);
|
||||
conn->request.keep_alive = 1;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void cleanup_test_connection(connection_t *conn) {
|
||||
buffer_free(&conn->read_buf);
|
||||
buffer_free(&conn->write_buf);
|
||||
memset(conn, 0, sizeof(connection_t));
|
||||
}
|
||||
|
||||
void test_dashboard_serve_null(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard Serve NULL Safety");
|
||||
|
||||
dashboard_serve(NULL, "GET /", 5);
|
||||
TEST_ASSERT(1, "NULL connection doesn't crash");
|
||||
|
||||
dashboard_serve_stats_api(NULL, "GET /", 5);
|
||||
TEST_ASSERT(1, "NULL connection for stats doesn't crash");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_dashboard_serve_html(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard Serve HTML");
|
||||
|
||||
epoll_fd = epoll_create1(0);
|
||||
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
|
||||
|
||||
monitor_init(NULL);
|
||||
auth_init(NULL, NULL);
|
||||
|
||||
connection_t *conn = create_test_connection();
|
||||
|
||||
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
dashboard_serve(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(conn->write_buf.tail > 0, "Response written to buffer");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "HTTP 200 response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "text/html") != NULL, "Content-Type is HTML");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "<!DOCTYPE html>") != NULL, "HTML content present");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
monitor_cleanup();
|
||||
close(epoll_fd);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_dashboard_serve_stats_api(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard Serve Stats API");
|
||||
|
||||
epoll_fd = epoll_create1(0);
|
||||
monitor_init(NULL);
|
||||
auth_init(NULL, NULL);
|
||||
|
||||
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("test.example.com");
|
||||
monitor_record_request_start(stats, 0);
|
||||
monitor_record_bytes(stats, 1000, 500);
|
||||
|
||||
connection_t *conn = create_test_connection();
|
||||
|
||||
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
const char *request = "GET /rproxy/api/stats HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
dashboard_serve_stats_api(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(conn->write_buf.tail > 0, "Stats response written");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "HTTP 200 response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "application/json") != NULL, "Content-Type is JSON");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "cpu_percent") != NULL, "CPU data in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "memory_gb") != NULL, "Memory data in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "active_connections") != NULL, "Connections data in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "processes") != NULL, "Processes array in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "test.example.com") != NULL, "Vhost in response");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
monitor_cleanup();
|
||||
close(epoll_fd);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_dashboard_with_auth_disabled(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard With Auth Disabled");
|
||||
|
||||
epoll_fd = epoll_create1(0);
|
||||
monitor_init(NULL);
|
||||
auth_init(NULL, NULL);
|
||||
|
||||
connection_t *conn = create_test_connection();
|
||||
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
dashboard_serve(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "Dashboard accessible without auth when disabled");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
monitor_cleanup();
|
||||
close(epoll_fd);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_dashboard_with_auth_enabled_no_header(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard With Auth Enabled No Header");
|
||||
|
||||
epoll_fd = epoll_create1(0);
|
||||
monitor_init(NULL);
|
||||
auth_init("admin", "secret");
|
||||
|
||||
connection_t *conn = create_test_connection();
|
||||
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
dashboard_serve(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 401") != NULL, "401 response when auth required");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "WWW-Authenticate") != NULL, "WWW-Authenticate header present");
|
||||
TEST_ASSERT(conn->state == CLIENT_STATE_ERROR, "Connection state is ERROR");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
auth_init(NULL, NULL);
|
||||
monitor_cleanup();
|
||||
close(epoll_fd);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_dashboard_with_valid_auth(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard With Valid Auth");
|
||||
|
||||
epoll_fd = epoll_create1(0);
|
||||
monitor_init(NULL);
|
||||
auth_init("admin", "secret");
|
||||
|
||||
connection_t *conn = create_test_connection();
|
||||
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic YWRtaW46c2VjcmV0\r\n\r\n";
|
||||
dashboard_serve(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "HTTP/1.1 200") != NULL, "200 response with valid auth");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "<!DOCTYPE html>") != NULL, "HTML content served");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
auth_init(NULL, NULL);
|
||||
monitor_cleanup();
|
||||
close(epoll_fd);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_dashboard_stats_with_history(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard Stats With History Data");
|
||||
|
||||
epoll_fd = epoll_create1(0);
|
||||
monitor_init(NULL);
|
||||
auth_init(NULL, NULL);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
monitor_update();
|
||||
}
|
||||
|
||||
vhost_stats_t *stats1 = monitor_get_or_create_vhost_stats("api.example.com");
|
||||
vhost_stats_t *stats2 = monitor_get_or_create_vhost_stats("web.example.com");
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
monitor_record_request_start(stats1, 0);
|
||||
monitor_record_request_start(stats2, 1);
|
||||
}
|
||||
monitor_record_bytes(stats1, 50000, 25000);
|
||||
monitor_record_bytes(stats2, 30000, 15000);
|
||||
|
||||
connection_t *conn = create_test_connection();
|
||||
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
const char *request = "GET /rproxy/api/stats HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
dashboard_serve_stats_api(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "api.example.com") != NULL, "First vhost in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "web.example.com") != NULL, "Second vhost in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "cpu_history") != NULL, "CPU history in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "memory_history") != NULL, "Memory history in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "network_rx_history") != NULL, "Network RX history in response");
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "disk_read_history") != NULL, "Disk read history in response");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
monitor_cleanup();
|
||||
close(epoll_fd);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_dashboard_keep_alive(void) {
|
||||
TEST_SUITE_BEGIN("Dashboard Keep-Alive Header");
|
||||
|
||||
epoll_fd = epoll_create1(0);
|
||||
monitor_init(NULL);
|
||||
auth_init(NULL, NULL);
|
||||
|
||||
connection_t *conn = create_test_connection();
|
||||
conn->request.keep_alive = 1;
|
||||
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn->fd };
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
const char *request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n";
|
||||
dashboard_serve(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "Connection: keep-alive") != NULL, "Keep-alive in response");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
|
||||
conn = create_test_connection();
|
||||
conn->request.keep_alive = 0;
|
||||
ev.data.fd = conn->fd;
|
||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->fd, &ev);
|
||||
|
||||
dashboard_serve(conn, request, strlen(request));
|
||||
|
||||
TEST_ASSERT(strstr((char*)conn->write_buf.data, "Connection: close") != NULL, "Connection close in response");
|
||||
|
||||
cleanup_test_connection(conn);
|
||||
monitor_cleanup();
|
||||
close(epoll_fd);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_dashboard_tests(void) {
|
||||
test_dashboard_serve_null();
|
||||
test_dashboard_serve_html();
|
||||
test_dashboard_serve_stats_api();
|
||||
test_dashboard_with_auth_disabled();
|
||||
test_dashboard_with_auth_enabled_no_header();
|
||||
test_dashboard_with_valid_auth();
|
||||
test_dashboard_stats_with_history();
|
||||
test_dashboard_keep_alive();
|
||||
}
|
||||
Executable → Regular
@@ -1,210 +0,0 @@
|
||||
#include "test_framework.h"
|
||||
#include "../src/types.h"
|
||||
#include "../src/health_check.h"
|
||||
#include "../src/config.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern app_config_t *config;
|
||||
|
||||
static void setup_test_config(int route_count) {
|
||||
if (config) {
|
||||
if (config->routes) free(config->routes);
|
||||
free(config);
|
||||
}
|
||||
|
||||
config = calloc(1, sizeof(app_config_t));
|
||||
config->port = 8080;
|
||||
config->route_count = route_count;
|
||||
|
||||
if (route_count > 0) {
|
||||
config->routes = calloc(route_count, sizeof(route_config_t));
|
||||
for (int i = 0; i < route_count; i++) {
|
||||
snprintf(config->routes[i].hostname, sizeof(config->routes[i].hostname), "host%d.example.com", i);
|
||||
snprintf(config->routes[i].upstream_host, sizeof(config->routes[i].upstream_host), "127.0.0.1");
|
||||
config->routes[i].upstream_port = 3000 + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup_test_config(void) {
|
||||
if (config) {
|
||||
if (config->routes) free(config->routes);
|
||||
free(config);
|
||||
config = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void test_health_check_init_no_routes(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Init No Routes");
|
||||
|
||||
setup_test_config(0);
|
||||
health_check_init();
|
||||
|
||||
int healthy = health_check_is_healthy("nonexistent.com");
|
||||
TEST_ASSERT_EQ(1, healthy, "Nonexistent host returns healthy (fail-open)");
|
||||
|
||||
health_check_cleanup();
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_init_with_routes(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Init With Routes");
|
||||
|
||||
setup_test_config(3);
|
||||
health_check_init();
|
||||
|
||||
int healthy0 = health_check_is_healthy("host0.example.com");
|
||||
TEST_ASSERT_EQ(1, healthy0, "Host0 initially healthy");
|
||||
|
||||
int healthy1 = health_check_is_healthy("host1.example.com");
|
||||
TEST_ASSERT_EQ(1, healthy1, "Host1 initially healthy");
|
||||
|
||||
int healthy2 = health_check_is_healthy("host2.example.com");
|
||||
TEST_ASSERT_EQ(1, healthy2, "Host2 initially healthy");
|
||||
|
||||
int unknown = health_check_is_healthy("unknown.example.com");
|
||||
TEST_ASSERT_EQ(1, unknown, "Unknown host returns healthy (fail-open)");
|
||||
|
||||
health_check_cleanup();
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_null_hostname(void) {
|
||||
TEST_SUITE_BEGIN("Health Check NULL Hostname");
|
||||
|
||||
setup_test_config(1);
|
||||
health_check_init();
|
||||
|
||||
int healthy = health_check_is_healthy(NULL);
|
||||
TEST_ASSERT_EQ(1, healthy, "NULL hostname returns healthy (fail-open)");
|
||||
|
||||
health_check_cleanup();
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_case_insensitive(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Case Insensitive");
|
||||
|
||||
setup_test_config(1);
|
||||
health_check_init();
|
||||
|
||||
int lower = health_check_is_healthy("host0.example.com");
|
||||
TEST_ASSERT_EQ(1, lower, "Lowercase matches");
|
||||
|
||||
int upper = health_check_is_healthy("HOST0.EXAMPLE.COM");
|
||||
TEST_ASSERT_EQ(1, upper, "Uppercase matches");
|
||||
|
||||
int mixed = health_check_is_healthy("Host0.Example.COM");
|
||||
TEST_ASSERT_EQ(1, mixed, "Mixed case matches");
|
||||
|
||||
health_check_cleanup();
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_cleanup(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Cleanup");
|
||||
|
||||
setup_test_config(2);
|
||||
health_check_init();
|
||||
|
||||
health_check_cleanup();
|
||||
|
||||
int healthy = health_check_is_healthy("host0.example.com");
|
||||
TEST_ASSERT_EQ(1, healthy, "After cleanup returns healthy (disabled)");
|
||||
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_run_no_init(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Run Without Init");
|
||||
|
||||
health_check_run();
|
||||
TEST_ASSERT(1, "Run without init doesn't crash");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_run_with_init(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Run With Init");
|
||||
|
||||
setup_test_config(1);
|
||||
health_check_init();
|
||||
|
||||
health_check_run();
|
||||
TEST_ASSERT(1, "Health check run completes");
|
||||
|
||||
int healthy = health_check_is_healthy("host0.example.com");
|
||||
TEST_ASSERT(healthy == 0 || healthy == 1, "Health status is valid");
|
||||
|
||||
health_check_cleanup();
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_multiple_init(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Multiple Init");
|
||||
|
||||
setup_test_config(2);
|
||||
health_check_init();
|
||||
|
||||
int h1 = health_check_is_healthy("host0.example.com");
|
||||
TEST_ASSERT_EQ(1, h1, "First init: host0 healthy");
|
||||
|
||||
setup_test_config(3);
|
||||
health_check_init();
|
||||
|
||||
int h2 = health_check_is_healthy("host2.example.com");
|
||||
TEST_ASSERT_EQ(1, h2, "Second init: host2 healthy");
|
||||
|
||||
health_check_cleanup();
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_health_check_disabled_state(void) {
|
||||
TEST_SUITE_BEGIN("Health Check Disabled State");
|
||||
|
||||
int healthy_before = health_check_is_healthy("any.host.com");
|
||||
TEST_ASSERT_EQ(1, healthy_before, "Disabled: any host is healthy");
|
||||
|
||||
setup_test_config(1);
|
||||
health_check_init();
|
||||
|
||||
int healthy_enabled = health_check_is_healthy("host0.example.com");
|
||||
TEST_ASSERT_EQ(1, healthy_enabled, "Enabled: known host is healthy");
|
||||
|
||||
health_check_cleanup();
|
||||
|
||||
int healthy_after = health_check_is_healthy("any.host.com");
|
||||
TEST_ASSERT_EQ(1, healthy_after, "After cleanup: any host is healthy");
|
||||
|
||||
cleanup_test_config();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void run_health_check_tests(void) {
|
||||
test_health_check_init_no_routes();
|
||||
test_health_check_init_with_routes();
|
||||
test_health_check_null_hostname();
|
||||
test_health_check_case_insensitive();
|
||||
test_health_check_cleanup();
|
||||
test_health_check_run_no_init();
|
||||
test_health_check_run_with_init();
|
||||
test_health_check_multiple_init();
|
||||
test_health_check_disabled_state();
|
||||
}
|
||||
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
-12
@@ -25,12 +25,6 @@ extern void run_http_helper_tests(void);
|
||||
extern void run_patch_tests(void);
|
||||
extern void run_auth_tests(void);
|
||||
extern void run_rate_limit_tests(void);
|
||||
extern void run_monitor_tests(void);
|
||||
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;
|
||||
@@ -50,12 +44,6 @@ int main(int argc, char *argv[]) {
|
||||
run_patch_tests();
|
||||
run_auth_tests();
|
||||
run_rate_limit_tests();
|
||||
run_monitor_tests();
|
||||
run_dashboard_tests();
|
||||
run_health_check_tests();
|
||||
run_ssl_handler_tests();
|
||||
run_connection_tests();
|
||||
run_logging_tests();
|
||||
|
||||
test_summary();
|
||||
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
#include "test_framework.h"
|
||||
#include "../src/types.h"
|
||||
#include "../src/monitor.h"
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void test_history_deque_init(void) {
|
||||
TEST_SUITE_BEGIN("History Deque Initialization");
|
||||
|
||||
history_deque_t dq;
|
||||
history_deque_init(&dq, 10);
|
||||
|
||||
TEST_ASSERT(dq.points != NULL, "Deque points allocated");
|
||||
TEST_ASSERT_EQ(10, dq.capacity, "Capacity is 10");
|
||||
TEST_ASSERT_EQ(0, dq.count, "Count is 0");
|
||||
TEST_ASSERT_EQ(0, dq.head, "Head is 0");
|
||||
|
||||
free(dq.points);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_history_deque_push(void) {
|
||||
TEST_SUITE_BEGIN("History Deque Push");
|
||||
|
||||
history_deque_t dq;
|
||||
history_deque_init(&dq, 5);
|
||||
|
||||
history_deque_push(&dq, 1.0, 100.0);
|
||||
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after first push");
|
||||
|
||||
history_deque_push(&dq, 2.0, 200.0);
|
||||
history_deque_push(&dq, 3.0, 300.0);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count is 3 after three pushes");
|
||||
|
||||
history_deque_push(&dq, 4.0, 400.0);
|
||||
history_deque_push(&dq, 5.0, 500.0);
|
||||
TEST_ASSERT_EQ(5, dq.count, "Count is 5 at capacity");
|
||||
|
||||
history_deque_push(&dq, 6.0, 600.0);
|
||||
TEST_ASSERT_EQ(5, dq.count, "Count stays at capacity after overflow");
|
||||
|
||||
free(dq.points);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_network_history_deque(void) {
|
||||
TEST_SUITE_BEGIN("Network History Deque");
|
||||
|
||||
network_history_deque_t dq;
|
||||
network_history_deque_init(&dq, 5);
|
||||
|
||||
TEST_ASSERT(dq.points != NULL, "Network deque points allocated");
|
||||
TEST_ASSERT_EQ(5, dq.capacity, "Capacity is 5");
|
||||
TEST_ASSERT_EQ(0, dq.count, "Count is 0");
|
||||
|
||||
network_history_deque_push(&dq, 1.0, 100.0, 50.0);
|
||||
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after push");
|
||||
|
||||
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 3");
|
||||
|
||||
free(dq.points);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_disk_history_deque(void) {
|
||||
TEST_SUITE_BEGIN("Disk History Deque");
|
||||
|
||||
disk_history_deque_t dq;
|
||||
disk_history_deque_init(&dq, 5);
|
||||
|
||||
TEST_ASSERT(dq.points != NULL, "Disk deque points allocated");
|
||||
TEST_ASSERT_EQ(5, dq.capacity, "Capacity is 5");
|
||||
|
||||
disk_history_deque_push(&dq, 1.0, 10.0, 5.0);
|
||||
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after push");
|
||||
|
||||
disk_history_deque_push(&dq, 2.0, 20.0, 10.0);
|
||||
TEST_ASSERT_EQ(2, dq.count, "Count is 2");
|
||||
|
||||
free(dq.points);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_request_time_deque(void) {
|
||||
TEST_SUITE_BEGIN("Request Time Deque");
|
||||
|
||||
request_time_deque_t dq;
|
||||
request_time_deque_init(&dq, 10);
|
||||
|
||||
TEST_ASSERT(dq.times != NULL, "Request time deque allocated");
|
||||
TEST_ASSERT_EQ(10, dq.capacity, "Capacity is 10");
|
||||
TEST_ASSERT_EQ(0, dq.count, "Count is 0");
|
||||
|
||||
request_time_deque_push(&dq, 15.5);
|
||||
TEST_ASSERT_EQ(1, dq.count, "Count is 1 after push");
|
||||
|
||||
request_time_deque_push(&dq, 20.0);
|
||||
request_time_deque_push(&dq, 25.5);
|
||||
TEST_ASSERT_EQ(3, dq.count, "Count is 3");
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
request_time_deque_push(&dq, (double)i);
|
||||
}
|
||||
TEST_ASSERT_EQ(10, dq.count, "Count stays at capacity");
|
||||
|
||||
free(dq.times);
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_init_cleanup(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Init and Cleanup");
|
||||
|
||||
monitor_init(NULL);
|
||||
|
||||
TEST_ASSERT(monitor.cpu_history.points != NULL, "CPU history initialized");
|
||||
TEST_ASSERT(monitor.memory_history.points != NULL, "Memory history initialized");
|
||||
TEST_ASSERT(monitor.network_history.points != NULL, "Network history initialized");
|
||||
TEST_ASSERT(monitor.disk_history.points != NULL, "Disk history initialized");
|
||||
|
||||
monitor_cleanup();
|
||||
|
||||
TEST_ASSERT(monitor.cpu_history.points == NULL, "CPU history cleaned up");
|
||||
TEST_ASSERT(monitor.memory_history.points == NULL, "Memory history cleaned up");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_vhost_stats(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Vhost Stats");
|
||||
|
||||
monitor_init(NULL);
|
||||
|
||||
vhost_stats_t *stats1 = monitor_get_or_create_vhost_stats("test.example.com");
|
||||
TEST_ASSERT(stats1 != NULL, "First vhost stats created");
|
||||
TEST_ASSERT_STR_EQ("test.example.com", stats1->vhost_name, "Vhost name correct");
|
||||
TEST_ASSERT_EQ(0, stats1->total_requests, "Initial total requests is 0");
|
||||
|
||||
vhost_stats_t *stats2 = monitor_get_or_create_vhost_stats("test.example.com");
|
||||
TEST_ASSERT(stats1 == stats2, "Same vhost returns same stats");
|
||||
|
||||
vhost_stats_t *stats3 = monitor_get_or_create_vhost_stats("other.example.com");
|
||||
TEST_ASSERT(stats3 != NULL, "Second vhost stats created");
|
||||
TEST_ASSERT(stats1 != stats3, "Different vhosts have different stats");
|
||||
|
||||
vhost_stats_t *null_stats = monitor_get_or_create_vhost_stats(NULL);
|
||||
TEST_ASSERT(null_stats == NULL, "NULL vhost returns NULL");
|
||||
|
||||
vhost_stats_t *empty_stats = monitor_get_or_create_vhost_stats("");
|
||||
TEST_ASSERT(empty_stats == NULL, "Empty vhost returns NULL");
|
||||
|
||||
monitor_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_record_request(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Record Request");
|
||||
|
||||
monitor_init(NULL);
|
||||
|
||||
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("api.test.com");
|
||||
TEST_ASSERT(stats != NULL, "Stats created");
|
||||
|
||||
monitor_record_request_start(stats, 0);
|
||||
TEST_ASSERT_EQ(1, stats->total_requests, "Total requests incremented");
|
||||
TEST_ASSERT_EQ(1, stats->http_requests, "HTTP requests incremented");
|
||||
TEST_ASSERT_EQ(0, stats->websocket_requests, "WebSocket requests unchanged");
|
||||
|
||||
monitor_record_request_start(stats, 1);
|
||||
TEST_ASSERT_EQ(2, stats->total_requests, "Total requests is 2");
|
||||
TEST_ASSERT_EQ(1, stats->http_requests, "HTTP requests still 1");
|
||||
TEST_ASSERT_EQ(1, stats->websocket_requests, "WebSocket requests is 1");
|
||||
|
||||
monitor_record_request_start(NULL, 0);
|
||||
TEST_ASSERT(1, "NULL stats doesn't crash");
|
||||
|
||||
monitor_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_record_bytes(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Record Bytes");
|
||||
|
||||
monitor_init(NULL);
|
||||
|
||||
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("bytes.test.com");
|
||||
TEST_ASSERT(stats != NULL, "Stats created");
|
||||
TEST_ASSERT_EQ(0, stats->bytes_sent, "Initial bytes sent is 0");
|
||||
TEST_ASSERT_EQ(0, stats->bytes_recv, "Initial bytes recv is 0");
|
||||
|
||||
monitor_record_bytes(stats, 1000, 500);
|
||||
TEST_ASSERT_EQ(1000, stats->bytes_sent, "Bytes sent recorded");
|
||||
TEST_ASSERT_EQ(500, stats->bytes_recv, "Bytes recv recorded");
|
||||
|
||||
monitor_record_bytes(stats, 500, 250);
|
||||
TEST_ASSERT_EQ(1500, stats->bytes_sent, "Bytes sent accumulated");
|
||||
TEST_ASSERT_EQ(750, stats->bytes_recv, "Bytes recv accumulated");
|
||||
|
||||
monitor_record_bytes(NULL, 100, 100);
|
||||
TEST_ASSERT(1, "NULL stats doesn't crash");
|
||||
|
||||
monitor_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_monitor_update(void) {
|
||||
TEST_SUITE_BEGIN("Monitor Update");
|
||||
|
||||
monitor_init(NULL);
|
||||
|
||||
monitor_update();
|
||||
TEST_ASSERT(monitor.cpu_history.count >= 0, "CPU history has data after update");
|
||||
TEST_ASSERT(monitor.memory_history.count >= 0, "Memory history has data after update");
|
||||
|
||||
monitor_update();
|
||||
monitor_update();
|
||||
TEST_ASSERT(1, "Multiple updates don't crash");
|
||||
|
||||
monitor_cleanup();
|
||||
|
||||
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();
|
||||
test_network_history_deque();
|
||||
test_disk_history_deque();
|
||||
test_request_time_deque();
|
||||
test_monitor_init_cleanup();
|
||||
test_monitor_vhost_stats();
|
||||
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
@@ -1,288 +0,0 @@
|
||||
#include "test_framework.h"
|
||||
#include "../src/types.h"
|
||||
#include "../src/ssl_handler.h"
|
||||
#include <string.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
extern SSL_CTX *ssl_ctx;
|
||||
|
||||
void test_ssl_init_cleanup(void) {
|
||||
TEST_SUITE_BEGIN("SSL Init and Cleanup");
|
||||
|
||||
ssl_init();
|
||||
TEST_ASSERT(ssl_ctx != NULL, "SSL context created");
|
||||
|
||||
ssl_cleanup();
|
||||
TEST_ASSERT(ssl_ctx == NULL, "SSL context cleaned up");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_multiple_init(void) {
|
||||
TEST_SUITE_BEGIN("SSL Multiple Init");
|
||||
|
||||
ssl_init();
|
||||
SSL_CTX *first_ctx = ssl_ctx;
|
||||
TEST_ASSERT(first_ctx != NULL, "First init creates context");
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
ssl_init();
|
||||
SSL_CTX *second_ctx = ssl_ctx;
|
||||
TEST_ASSERT(second_ctx != NULL, "Second init creates context");
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_set_verify(void) {
|
||||
TEST_SUITE_BEGIN("SSL Set Verify");
|
||||
|
||||
ssl_set_verify(1);
|
||||
TEST_ASSERT(1, "Set verify enabled doesn't crash");
|
||||
|
||||
ssl_set_verify(0);
|
||||
TEST_ASSERT(1, "Set verify disabled doesn't crash");
|
||||
|
||||
ssl_init();
|
||||
TEST_ASSERT(ssl_ctx != NULL, "Context created after set_verify");
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_set_ca_file(void) {
|
||||
TEST_SUITE_BEGIN("SSL Set CA File");
|
||||
|
||||
ssl_set_ca_file(NULL);
|
||||
TEST_ASSERT(1, "NULL CA file doesn't crash");
|
||||
|
||||
ssl_set_ca_file("/nonexistent/path/ca.crt");
|
||||
TEST_ASSERT(1, "Nonexistent CA file doesn't crash");
|
||||
|
||||
ssl_set_ca_file("/etc/ssl/certs/ca-certificates.crt");
|
||||
TEST_ASSERT(1, "System CA file path set");
|
||||
|
||||
ssl_init();
|
||||
TEST_ASSERT(ssl_ctx != NULL, "Context created after set_ca_file");
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_set_ca_path(void) {
|
||||
TEST_SUITE_BEGIN("SSL Set CA Path");
|
||||
|
||||
ssl_set_ca_path(NULL);
|
||||
TEST_ASSERT(1, "NULL CA path doesn't crash");
|
||||
|
||||
ssl_set_ca_path("/nonexistent/path");
|
||||
TEST_ASSERT(1, "Nonexistent CA path doesn't crash");
|
||||
|
||||
ssl_set_ca_path("/etc/ssl/certs");
|
||||
TEST_ASSERT(1, "System CA path set");
|
||||
|
||||
ssl_init();
|
||||
TEST_ASSERT(ssl_ctx != NULL, "Context created after set_ca_path");
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_context_options(void) {
|
||||
TEST_SUITE_BEGIN("SSL Context Options");
|
||||
|
||||
ssl_set_verify(0);
|
||||
ssl_init();
|
||||
|
||||
TEST_ASSERT(ssl_ctx != NULL, "Context created");
|
||||
|
||||
if (ssl_ctx) {
|
||||
long options = SSL_CTX_get_options(ssl_ctx);
|
||||
TEST_ASSERT((options & SSL_OP_NO_SSLv3) != 0, "SSLv3 disabled");
|
||||
}
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_create_connection(void) {
|
||||
TEST_SUITE_BEGIN("SSL Create Connection Object");
|
||||
|
||||
ssl_set_verify(0);
|
||||
ssl_init();
|
||||
|
||||
if (ssl_ctx) {
|
||||
SSL *ssl = SSL_new(ssl_ctx);
|
||||
TEST_ASSERT(ssl != NULL, "SSL object created from context");
|
||||
|
||||
if (ssl) {
|
||||
SSL_free(ssl);
|
||||
}
|
||||
} else {
|
||||
TEST_ASSERT(1, "Skipped - no SSL context");
|
||||
}
|
||||
|
||||
ssl_cleanup();
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_handshake_null(void) {
|
||||
TEST_SUITE_BEGIN("SSL Handshake NULL Safety");
|
||||
|
||||
int result = ssl_do_handshake(NULL);
|
||||
TEST_ASSERT_EQ(-1, result, "NULL connection returns -1");
|
||||
|
||||
connection_t conn;
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
conn.ssl = NULL;
|
||||
|
||||
result = ssl_do_handshake(&conn);
|
||||
TEST_ASSERT_EQ(-1, result, "Connection without SSL returns -1");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_read_write_null(void) {
|
||||
TEST_SUITE_BEGIN("SSL Read/Write NULL Safety");
|
||||
|
||||
char buf[100];
|
||||
|
||||
int read_result = ssl_read(NULL, buf, sizeof(buf));
|
||||
TEST_ASSERT_EQ(-1, read_result, "NULL connection read returns -1");
|
||||
|
||||
int write_result = ssl_write(NULL, buf, sizeof(buf));
|
||||
TEST_ASSERT_EQ(-1, write_result, "NULL connection write returns -1");
|
||||
|
||||
connection_t conn;
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
conn.ssl = NULL;
|
||||
|
||||
read_result = ssl_read(&conn, buf, sizeof(buf));
|
||||
TEST_ASSERT_EQ(-1, read_result, "Connection without SSL read returns -1");
|
||||
|
||||
write_result = ssl_write(&conn, buf, sizeof(buf));
|
||||
TEST_ASSERT_EQ(-1, write_result, "Connection without SSL write returns -1");
|
||||
|
||||
TEST_SUITE_END();
|
||||
}
|
||||
|
||||
void test_ssl_reinit(void) {
|
||||
TEST_SUITE_BEGIN("SSL Reinitialize");
|
||||
|
||||
ssl_set_verify(0);
|
||||
ssl_init();
|
||||
TEST_ASSERT(ssl_ctx != NULL, "First init with verify=0");
|
||||
ssl_cleanup();
|
||||
|
||||
ssl_set_verify(1);
|
||||
ssl_set_ca_path("/etc/ssl/certs");
|
||||
ssl_init();
|
||||
TEST_ASSERT(ssl_ctx != NULL, "Second init with verify=1");
|
||||
ssl_cleanup();
|
||||
|
||||
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();
|
||||
test_ssl_set_verify();
|
||||
test_ssl_set_ca_file();
|
||||
test_ssl_set_ca_path();
|
||||
test_ssl_context_options();
|
||||
test_ssl_create_connection();
|
||||
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