diff --git a/rproxy.c b/rproxy.c index c0b224c..8dffd59 100644 --- a/rproxy.c +++ b/rproxy.c @@ -1697,6 +1697,7 @@ void accept_new_connection(int listener_fd) { } } + void close_connection(int fd) { if (fd < 0 || fd >= MAX_FDS) return; @@ -1706,34 +1707,12 @@ void close_connection(int fd) { // Prevent double-closing if (conn->fd == -1) return; - connection_t *pair = conn->pair; - - // --- START: ROBUST STATE & PAIR MANAGEMENT FIX --- - - // Decouple the pair immediately to prevent circular logic. - if (pair) { - pair->pair = NULL; + int pair_fd = -1; + if (conn->pair && conn->pair->fd != -1) { + pair_fd = conn->pair->fd; + conn->pair->pair = NULL; } - // If the connection being closed is a CLIENT, we must also close its upstream pair. - if (conn->type == CONN_TYPE_CLIENT && pair) { - close_connection(pair->fd); - } - - // If the connection being closed is an UPSTREAM, its client pair must be - // reset to handle the next keep-alive request. This is the core of the fix. - if (conn->type == CONN_TYPE_UPSTREAM && pair && pair->type == CONN_TYPE_CLIENT) { - log_info("[STATE-FIX] Upstream fd=%d closed. Resetting client fd=%d for next request.", fd, pair->fd); - pair->state = CLIENT_STATE_READING_HEADERS; - - // A new request might have been read into the buffer due to the race condition. - // We must attempt to process it now to serve it correctly. - if (buffer_available_read(&pair->read_buf) > 0) { - handle_client_read(pair); - } - } - // --- END: ROBUST STATE & PAIR MANAGEMENT FIX --- - // Record request end if needed if (conn->vhost_stats && conn->request_start_time > 0) { monitor_record_request_end(conn->vhost_stats, conn->request_start_time); @@ -1748,7 +1727,7 @@ void close_connection(int fd) { } } - log_debug("Closing connection on fd %d, remaining: %d", fd, monitor.active_connections); + log_debug("Closing connection on fd %d, pair %d, remaining: %d", fd, pair_fd, monitor.active_connections); // Remove from epoll before closing epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); @@ -1771,11 +1750,17 @@ void close_connection(int fd) { memset(conn, 0, sizeof(connection_t)); conn->type = CONN_TYPE_UNUSED; conn->fd = -1; + + // Close the paired connection + if (pair_fd != -1) { + close_connection(pair_fd); + } } + void connect_to_upstream(connection_t *client, const char *data, size_t data_len) { if (!client || !data) return; @@ -2114,13 +2099,10 @@ void handle_client_read(connection_t *conn) { static void handle_forwarding(connection_t *conn) { connection_t *pair = conn->pair; if (!pair || pair->fd == -1) { - // This is an orphaned connection. If it's a client, reset it to read new requests. - // Otherwise, it's a dead upstream connection that should be closed. if (conn->type == CONN_TYPE_CLIENT) { log_info("[ROUTING-ORPHAN] Client fd=%d lost upstream, resetting to READING_HEADERS", conn->fd); conn->state = CLIENT_STATE_READING_HEADERS; conn->pair = NULL; - // Process any data that was received while it was orphaned. if (buffer_available_read(&conn->read_buf) > 0) { handle_client_read(conn); } @@ -2130,13 +2112,48 @@ static void handle_forwarding(connection_t *conn) { return; } - // 1. Read data from the source connection. int bytes_read = do_read(conn); - - // 2. Forward any data we just read to the paired connection. + if (bytes_read <= 0 && (errno != EAGAIN && errno != EWOULDBLOCK)) { + close_connection(conn->fd); + return; + } + + // --- START: THE CHECKPOINT FIX FOR RACE CONDITION --- + if (bytes_read > 0 && conn->type == CONN_TYPE_CLIENT) { + // Before forwarding, we MUST check if the data is a new pipelined request. + char *data_start = conn->read_buf.data + conn->read_buf.head; + size_t data_len = buffer_available_read(&conn->read_buf); + + // A simple but highly effective check for the start of a new HTTP request. + if (data_len > 4 && ( + strncmp(data_start, "GET ", 4) == 0 || + strncmp(data_start, "POST ", 5) == 0 || + strncmp(data_start, "PUT ", 4) == 0 || + strncmp(data_start, "DELETE ", 7) == 0 || + strncmp(data_start, "HEAD ", 5) == 0 || + strncmp(data_start, "OPTIONS ", 8) == 0 + )) { + log_info("[STATE-FIX] Checkpoint: Pipelined request detected from client fd=%d. Halting forwarding.", conn->fd); + + // This is a new request. We must stop forwarding immediately. + // 1. Close the connection for the PREVIOUS request. + close_connection(pair->fd); + + // 2. Reset the client's state to parsing mode. + conn->state = CLIENT_STATE_READING_HEADERS; + conn->pair = NULL; // The link to the old upstream is now severed. + + // 3. Immediately process the new request that's already in the buffer. + handle_client_read(conn); + return; // Stop execution in this function. + } + } + // --- END: THE CHECKPOINT FIX --- + + // If the checkpoint is passed, the data is part of the current request body + // (or is data from the upstream), so we forward it as intended. size_t data_to_forward = buffer_available_read(&conn->read_buf); if (data_to_forward > 0) { - // --- SYNTAX FIX IS HERE --- if (buffer_ensure_capacity(&pair->write_buf, pair->write_buf.tail + data_to_forward) < 0) { log_error("Failed to grow write buffer for forwarding"); close_connection(conn->fd); @@ -2147,21 +2164,15 @@ static void handle_forwarding(connection_t *conn) { conn->read_buf.data + conn->read_buf.head, data_to_forward); pair->write_buf.tail += data_to_forward; - // --- END OF SYNTAX FIX --- buffer_consume(&conn->read_buf, data_to_forward); modify_epoll(pair->fd, EPOLLIN | EPOLLOUT); } - - // 3. On any read error or EOF, close the connection. - // The new logic in `close_connection` will correctly handle the state of the pair. - if (bytes_read <= 0 && (errno != EAGAIN && errno != EWOULDBLOCK)) { - close_connection(conn->fd); - } } + static void handle_ssl_handshake(connection_t *conn) { if (!conn->ssl || conn->ssl_handshake_done) return;