This commit is contained in:
retoor 2025-11-20 06:27:32 +01:00
parent bbfc462ec2
commit 51f2ade810

221
rproxy.c
View File

@ -276,13 +276,14 @@ static inline size_t buffer_available_write(buffer_t *buf) {
return buf->capacity - buf->tail;
}
static inline int buffer_ensure_capacity(buffer_t *buf, size_t required) {
if (buf->capacity >= required) return 0;
size_t new_capacity = buf->capacity;
while (new_capacity < required) {
new_capacity *= 2;
if (new_capacity > 1024*1024*10) { // 10MB max
if (new_capacity > SIZE_MAX / 2) {
log_error("Buffer size limit exceeded");
return -1;
}
@ -298,6 +299,7 @@ static inline int buffer_ensure_capacity(buffer_t *buf, size_t required) {
return 0;
}
// Compacts the buffer by moving unread data to the beginning
static inline void buffer_compact(buffer_t *buf) {
if (buf->head == 0) return;
@ -1770,7 +1772,6 @@ void close_connection(int fd) {
conn->fd = -1;
}
void connect_to_upstream(connection_t *client, const char *data, size_t data_len) {
if (!client || !data) return;
@ -1797,7 +1798,6 @@ void connect_to_upstream(connection_t *client, const char *data, size_t data_len
addr.sin_family = AF_INET;
addr.sin_port = htons(route->upstream_port);
// Resolve hostname
if (inet_pton(AF_INET, route->upstream_host, &addr.sin_addr) <= 0) {
struct hostent *he = gethostbyname(route->upstream_host);
if (!he) {
@ -1836,7 +1836,6 @@ void connect_to_upstream(connection_t *client, const char *data, size_t data_len
return;
}
// Prepare data to send
char *data_to_send = (char*)data;
size_t len_to_send = data_len;
char *modified_request = NULL;
@ -1846,7 +1845,6 @@ void connect_to_upstream(connection_t *client, const char *data, size_t data_len
const char *old_host_header_start = NULL;
const char *old_host_header_end = NULL;
// Find the old host header
const char *current = data;
const char *end = data + data_len;
while(current < end) {
@ -1873,21 +1871,20 @@ void connect_to_upstream(connection_t *client, const char *data, size_t data_len
modified_request = malloc(len_to_send + 1);
if (modified_request) {
char* p = modified_request;
// Copy part before Host header
size_t prefix_len = old_host_header_start - data;
memcpy(p, data, prefix_len);
p += prefix_len;
// Copy new Host header
memcpy(p, new_host_header, new_host_len);
p += new_host_len;
// Copy part after Host header
size_t suffix_len = data_len - (old_host_header_end - data);
memcpy(p, old_host_header_end, suffix_len);
data_to_send = modified_request;
}
}
}
// For non-SSL connections, buffer the request immediately
// For SSL connections, buffer it but don't send until handshake is complete
if (buffer_ensure_capacity(&up->write_buf, len_to_send) == 0) {
memcpy(up->write_buf.data, data_to_send, len_to_send);
up->write_buf.tail = len_to_send;
@ -1903,19 +1900,28 @@ void connect_to_upstream(connection_t *client, const char *data, size_t data_len
close_connection(client->fd);
return;
}
SSL_set_tlsext_host_name(up->ssl, route->upstream_host); // SNI
const char *sni_hostname = route->rewrite_host ? route->upstream_host : client->request.host;
SSL_set_tlsext_host_name(up->ssl, sni_hostname);
SSL_set_fd(up->ssl, up_fd);
SSL_set_connect_state(up->ssl);
// Don't mark handshake as done - it needs to complete first
up->ssl_handshake_done = 0;
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)",
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");
}
static int do_read(connection_t *conn) {
if (!conn) return -1;
@ -1988,6 +1994,7 @@ static int do_write(connection_t *conn) {
return written;
}
void handle_client_read(connection_t *conn) {
int bytes_read = do_read(conn);
if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
@ -1998,15 +2005,56 @@ void handle_client_read(connection_t *conn) {
buffer_t *buf = &conn->read_buf;
// If we're in FORWARDING state but lost our pair, reset to reading headers
if (conn->state == CLIENT_STATE_FORWARDING && conn->pair == NULL) {
conn->state = CLIENT_STATE_READING_HEADERS;
}
if (conn->state == CLIENT_STATE_FORWARDING) {
return;
// REMOVED: The early return for CLIENT_STATE_FORWARDING
// We need to check for new requests even when forwarding
// For FORWARDING state with an active upstream, we need to check if this
// is actually a new pipelined request or continuation of current request
if (conn->state == CLIENT_STATE_FORWARDING && conn->pair != NULL) {
// Check if we have a complete HTTP request in the buffer
char *data_start = buf->data + buf->head;
size_t data_len = buffer_available_read(buf);
// Look for HTTP method at the start - indicates a new request
if (data_len >= 4) {
int looks_like_new_request = 0;
if (strncmp(data_start, "GET ", 4) == 0 ||
strncmp(data_start, "POST ", 5) == 0 ||
strncmp(data_start, "PUT ", 4) == 0 ||
strncmp(data_start, "HEAD ", 5) == 0 ||
strncmp(data_start, "DELETE ", 7) == 0 ||
strncmp(data_start, "OPTIONS ", 8) == 0 ||
strncmp(data_start, "PATCH ", 6) == 0) {
looks_like_new_request = 1;
}
if (!looks_like_new_request) {
// This is continuation data for the current request/response
// Just return and let handle_forwarding deal with it
return;
}
// This is a pipelined request - we need to handle it
// First, close the existing upstream connection
log_debug("Pipelined request detected on fd %d, closing upstream fd %d",
conn->fd, conn->pair->fd);
close_connection(conn->pair->fd);
conn->pair = NULL;
conn->state = CLIENT_STATE_READING_HEADERS;
// Fall through to process the new request
} else {
// Not enough data to determine, treat as forwarding data
return;
}
}
while (buffer_available_read(buf) > 0) {
// Process requests when in READING_HEADERS state
while (buffer_available_read(buf) > 0 && conn->state == CLIENT_STATE_READING_HEADERS) {
char *data_start = buf->data + buf->head;
size_t data_len = buffer_available_read(buf);
@ -2035,70 +2083,72 @@ void handle_client_read(connection_t *conn) {
long long body_len = (conn->request.content_length > 0) ? conn->request.content_length : 0;
size_t total_request_len = headers_len + body_len;
// --- START GIT CLIENT FIX ---
// For chunked requests (like git push), we don't know the body length.
// We must forward immediately after headers. The 'is_chunked' flag allows us to skip the body-length check.
if (!conn->request.is_chunked && data_len < total_request_len) {
// Body is not fully received yet, wait for more data.
log_debug("fd %d: Incomplete body, waiting for more data.", conn->fd);
break;
}
// For chunked requests, we forward only the headers we have received.
// The rest of the chunked body will be streamed by handle_forwarding.
size_t len_to_forward = (conn->request.is_chunked) ? headers_len : total_request_len;
// --- END GIT CLIENT FIX ---
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
conn->request_start_time = ts.tv_sec + ts.tv_nsec / 1e9;
// Check for internal routes
if (strcmp(conn->request.method, "GET") == 0 &&
(strncmp(conn->request.uri, "/dashboard", 10) == 0 || strncmp(conn->request.uri, "/api/stats", 10) == 0)) {
(strncmp(conn->request.uri, "/dashboard", 10) == 0 ||
strncmp(conn->request.uri, "/api/stats", 10) == 0)) {
// ... (rest of this if-block is unchanged)
log_info("[ROUTING-INTERNAL] Serving internal route %s for fd=%d", conn->request.uri, conn->fd);
log_info("[ROUTING-INTERNAL] Serving internal route %s for fd=%d",
conn->request.uri, conn->fd);
// Make sure no upstream is connected
if (conn->pair) {
close_connection(conn->pair->fd);
conn->pair = NULL;
}
conn->state = CLIENT_STATE_SERVING_INTERNAL;
if (strncmp(conn->request.uri, "/dashboard", 10) == 0) {
serve_dashboard(conn);
} else {
serve_stats_api(conn);
}
buffer_consume(buf, total_request_len);
if (!conn->request.keep_alive) {
conn->state = CLIENT_STATE_CLOSING;
return;
}
conn->state = CLIENT_STATE_READING_HEADERS;
continue;
continue; // Process next pipelined request if any
}
log_info("[ROUTING-FORWARD] Forwarding request for fd=%d: %s %s", conn->fd, conn->request.method, conn->request.uri);
// Forward to upstream
log_info("[ROUTING-FORWARD] Forwarding request for fd=%d: %s %s",
conn->fd, conn->request.method, conn->request.uri);
conn->vhost_stats = monitor_get_or_create_vhost_stats(conn->request.host);
monitor_record_request_start(conn->vhost_stats, conn->request.is_websocket);
conn->state = CLIENT_STATE_FORWARDING;
// --- MODIFIED LINE ---
connect_to_upstream(conn, data_start, len_to_forward);
// --- MODIFIED LINE ---
buffer_consume(buf, len_to_forward);
return;
return; // Wait for upstream connection
}
}
static void handle_forwarding(connection_t *conn) {
connection_t *pair = conn->pair;
if (!pair || pair->fd == -1) {
if (conn->type == CONN_TYPE_CLIENT) {
log_info("[ROUTING-ORPHAN] Client fd=%d lost upstream, resetting to READING_HEADERS", conn->fd);
log_info("ROUTING-ORPHAN: Client fd=%d lost upstream, resetting to READING_HEADERS", conn->fd);
conn->state = CLIENT_STATE_READING_HEADERS;
conn->pair = NULL;
if (buffer_available_read(&conn->read_buf) > 0) {
@ -2112,77 +2162,60 @@ static void handle_forwarding(connection_t *conn) {
int bytes_read = do_read(conn);
// --- START: COMPLETE PIPELINE FIX ---
// This is the crucial fix for the keep-alive pipelining problem.
// If we read new data FROM THE CLIENT, we must inspect it before forwarding.
if (bytes_read > 0 && conn->type == CONN_TYPE_CLIENT) {
char* data_start = conn->read_buf.data + conn->read_buf.head;
size_t data_len = buffer_available_read(&conn->read_buf);
// Heuristic check: Does the new data look like a new HTTP request for an internal route?
// We check for "GET /" and then the specific paths.
if (data_len > 10 && memcmp(data_start, "GET /", 5) == 0) {
// Temporarily null-terminate to use strncmp safely on the URI part.
char old_char = data_start[data_len-1];
data_start[data_len-1] = '\0';
bool is_internal_route = (strstr(data_start, "/dashboard") || strstr(data_start, "/api/stats"));
data_start[data_len-1] = old_char; // Restore buffer
if (is_internal_route) {
log_info("[ROUTING-INTERCEPT] Intercepted internal route on keep-alive fd=%d. Re-routing.", conn->fd);
// This is an internal request. We must break the link to the old upstream server.
// The close_connection logic will handle un-pairing and resetting our state.
close_connection(pair->fd);
// The close_connection call above should have already reset this client's state
// and called handle_client_read if data was present. We can safely return.
return;
}
}
}
// --- END: COMPLETE PIPELINE FIX ---
if (bytes_read == 0) { // EOF received
if (bytes_read == 0) {
log_debug("EOF on fd %d, performing half-close on pair fd %d", conn->fd, pair->fd);
conn->half_closed = 1;
modify_epoll(conn->fd, buffer_available_read(&conn->write_buf) ? EPOLLOUT : 0);
if (pair->fd != -1 && !pair->write_shutdown) {
if (shutdown(pair->fd, SHUT_WR) == -1 && errno != ENOTCONN) {
log_debug("shutdown(SHUT_WR) failed for fd %d: %s", pair->fd, strerror(errno));
}
pair->write_shutdown = 1;
}
if (pair->half_closed) {
close_connection(conn->fd);
}
return;
}
if (bytes_read < 0 && (errno != EAGAIN && errno != EWOULDBLOCK)) {
if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
close_connection(conn->fd);
return;
}
size_t data_to_forward = buffer_available_read(&conn->read_buf);
if (data_to_forward > 0) {
if (buffer_ensure_capacity(&pair->write_buf, pair->write_buf.tail + data_to_forward) < 0) {
// CRITICAL: Try to write immediately if pair buffer is getting large
if (buffer_available_read(&pair->write_buf) > CHUNK_SIZE / 2) {
do_write(pair);
}
// Only buffer if write succeeded or socket would block
size_t space_needed = pair->write_buf.tail + data_to_forward;
if (buffer_ensure_capacity(&pair->write_buf, space_needed) < 0) {
log_debug("Failed to buffer data for fd %d, closing connection", conn->fd);
//log_error("Failed to allocate buffer for %zu bytes", space_needed);
close_connection(conn->fd);
return;
}
memcpy(pair->write_buf.data + pair->write_buf.tail,
conn->read_buf.data + conn->read_buf.head,
memcpy(pair->write_buf.data + pair->write_buf.tail,
conn->read_buf.data + conn->read_buf.head,
data_to_forward);
pair->write_buf.tail += data_to_forward;
buffer_consume(&conn->read_buf, data_to_forward);
// Try to flush immediately
do_write(pair);
modify_epoll(pair->fd, EPOLLIN | EPOLLOUT);
}
}
static void handle_ssl_handshake(connection_t *conn) {
if (!conn->ssl || conn->ssl_handshake_done) return;
@ -2191,9 +2224,30 @@ static void handle_ssl_handshake(connection_t *conn) {
conn->ssl_handshake_done = 1;
log_debug("SSL handshake completed for fd %d", conn->fd);
// If this is an upstream connection and we have buffered request data, send it now
if (conn->type == CONN_TYPE_UPSTREAM && conn->pair) {
connection_t *client = conn->pair;
// Check if we need to forward the initial request
// The request might still be in the client's read buffer if we delayed it for SSL
if (buffer_available_read(&client->read_buf) > 0) {
size_t data_len = buffer_available_read(&client->read_buf);
if (buffer_ensure_capacity(&conn->write_buf, conn->write_buf.tail + data_len) == 0) {
memcpy(conn->write_buf.data + conn->write_buf.tail,
client->read_buf.data + client->read_buf.head,
data_len);
conn->write_buf.tail += data_len;
buffer_consume(&client->read_buf, data_len);
log_debug("Forwarding %zu bytes of buffered request data after SSL handshake", data_len);
}
}
}
// Process any pending data
if (buffer_available_read(&conn->write_buf) > 0) {
modify_epoll(conn->fd, EPOLLIN | EPOLLOUT);
} else {
modify_epoll(conn->fd, EPOLLIN);
}
} else {
int ssl_error = SSL_get_error(conn->ssl, ret);
@ -2216,8 +2270,12 @@ static void handle_ssl_handshake(connection_t *conn) {
static void handle_write_event(connection_t *conn) {
conn->last_activity = time(NULL);
if (conn->type == CONN_TYPE_UPSTREAM && !conn->ssl_handshake_done) {
// ... (this section is unchanged)
// Handle SSL handshake for upstream connections
if (conn->type == CONN_TYPE_UPSTREAM && conn->ssl && !conn->ssl_handshake_done) {
handle_ssl_handshake(conn);
if (!conn->ssl_handshake_done) {
return; // Still waiting for handshake
}
}
int written = do_write(conn);
@ -2252,6 +2310,12 @@ static void handle_write_event(connection_t *conn) {
void handle_connection_event(struct epoll_event *event) {
int fd = event->data.fd;
if (fd < 0 || fd >= MAX_FDS) return;
@ -2346,6 +2410,7 @@ void create_default_config_if_not_exists(const char* filename) {
log_info("Created default config file: %s", filename);
}
void init_ssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
@ -2358,8 +2423,12 @@ void init_ssl() {
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
// Add this to ensure hostname verification is also disabled
SSL_CTX_set_verify_depth(ssl_ctx, 0);
}
void cleanup() {
log_info("Cleaning up resources...");