| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <string.h> | ||
| 4 | #include <poll.h> | ||
| 5 | #include <unistd.h> | ||
| 6 | #include <arpa/inet.h> | ||
| 7 | #include <sys/epoll.h> | ||
| 8 | #include <pthread.h> | ||
| 9 | #include <fcntl.h> | ||
| 10 | #include <liburing.h> | ||
| 11 | #include <sys/sendfile.h> | ||
| 12 | #include <sys/time.h> // for instrumentation | ||
| 13 | #include <signal.h> | ||
| 14 | #include <errno.h> | ||
| 15 | #include "config.h" | ||
| 16 | #include "server.h" | ||
| 17 | #include "http_parser.h" | ||
| 18 | #include "router.h" | ||
| 19 | #include "tls.h" | ||
| 20 | #include <openssl/ssl.h> | ||
| 21 | #include <openssl/err.h> | ||
| 22 | #include "thread_pool.h" | ||
| 23 | #include "log.h" | ||
| 24 | #include <ctype.h> | ||
| 25 | #include "http2_response.h" | ||
| 26 | |||
| 27 | #ifndef DEBUG_H2 | ||
| 28 | #define DEBUG_H2 0 | ||
| 29 | #endif | ||
| 30 | |||
| 31 | #define H2_LOG(...) \ | ||
| 32 | do { \ | ||
| 33 | if (DEBUG_H2) \ | ||
| 34 | log_message(LOG_LEVEL_DEBUG, __VA_ARGS__); \ | ||
| 35 | } while (0) | ||
| 36 | |||
| 37 | typedef struct { | ||
| 38 | SSL *ssl; | ||
| 39 | ServerConfig *config; | ||
| 40 | int want_read; | ||
| 41 | int want_write; | ||
| 42 | size_t total_read; | ||
| 43 | int logged_preface; | ||
| 44 | } H2IO; | ||
| 45 | |||
| 46 | 6 | static int find_header_end(const char *buf, size_t len) | |
| 47 | { | ||
| 48 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (len < 4) |
| 49 | ✗ | return -1; | |
| 50 |
2/2✓ Branch 0 taken 12469 times.
✓ Branch 1 taken 1 times.
|
12470 | for (size_t i = 0; i + 3 < len; i++) |
| 51 | { | ||
| 52 |
3/4✓ Branch 0 taken 308 times.
✓ Branch 1 taken 12161 times.
✓ Branch 2 taken 308 times.
✗ Branch 3 not taken.
|
12469 | if (buf[i] == '\r' && buf[i + 1] == '\n' && |
| 53 |
3/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 303 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
308 | buf[i + 2] == '\r' && buf[i + 3] == '\n') |
| 54 | 5 | return (int)(i + 4); | |
| 55 | } | ||
| 56 | 1 | return -1; | |
| 57 | } | ||
| 58 | |||
| 59 | /* Callback invoked for each header received in an HTTP/2 frame */ | ||
| 60 | 12 | static int on_header_callback(nghttp2_session *session, | |
| 61 | const nghttp2_frame *frame, | ||
| 62 | const uint8_t *name, size_t namelen, | ||
| 63 | const uint8_t *value, size_t valuelen, | ||
| 64 | uint8_t flags, void *user_data) | ||
| 65 | { | ||
| 66 | (void)session; | ||
| 67 | (void)flags; | ||
| 68 | (void)user_data; | ||
| 69 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (frame->hd.type == NGHTTP2_HEADERS && |
| 70 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | frame->headers.cat == NGHTTP2_HCAT_REQUEST) |
| 71 | { | ||
| 72 | 12 | StreamData *data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); | |
| 73 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
|
12 | if (!data) |
| 74 | { | ||
| 75 | 3 | data = calloc(1, sizeof(StreamData)); | |
| 76 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!data) |
| 77 | { | ||
| 78 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to allocate HTTP/2 stream state"); | |
| 79 | ✗ | return NGHTTP2_ERR_CALLBACK_FAILURE; | |
| 80 | } | ||
| 81 | 3 | data->req.version = strdup("HTTP/2"); | |
| 82 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!data->req.version) |
| 83 | { | ||
| 84 | ✗ | free(data); | |
| 85 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to allocate HTTP/2 request version"); | |
| 86 | ✗ | return NGHTTP2_ERR_CALLBACK_FAILURE; | |
| 87 | } | ||
| 88 | 3 | nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, data); | |
| 89 | } | ||
| 90 |
2/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
12 | if (namelen >= 1 && name[0] == ':') |
| 91 | { | ||
| 92 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
|
12 | if (strncmp((const char *)name, ":method", namelen) == 0) |
| 93 | { | ||
| 94 | 3 | free((void *)data->req.method); | |
| 95 | 3 | data->req.method = strndup((const char *)value, valuelen); | |
| 96 | } | ||
| 97 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
|
9 | else if (strncmp((const char *)name, ":path", namelen) == 0) |
| 98 | { | ||
| 99 | 3 | free((void *)data->req.path); | |
| 100 | 3 | data->req.path = strndup((const char *)value, valuelen); | |
| 101 | } | ||
| 102 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | else if (strncmp((const char *)name, ":scheme", namelen) == 0) |
| 103 | ; /* ignore scheme */ | ||
| 104 | 3 | else if (strncmp((const char *)name, ":authority", namelen) == 0) | |
| 105 | { | ||
| 106 | /* authority not currently used by routing */ | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | 12 | return 0; | |
| 111 | } | ||
| 112 | |||
| 113 | 3 | static ssize_t http2_body_read_callback( | |
| 114 | nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, | ||
| 115 | uint32_t *data_flags, nghttp2_data_source *source, void *user_data) | ||
| 116 | { | ||
| 117 | (void)session; | ||
| 118 | (void)stream_id; | ||
| 119 | (void)user_data; | ||
| 120 | 3 | StreamData *data = (StreamData *)source->ptr; | |
| 121 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
3 | if (!data || !data->resp) |
| 122 | { | ||
| 123 | ✗ | *data_flags = NGHTTP2_DATA_FLAG_EOF; | |
| 124 | ✗ | return 0; | |
| 125 | } | ||
| 126 | 3 | size_t remaining = data->resp->body_len - data->resp_sent; | |
| 127 | 3 | size_t to_copy = remaining < length ? remaining : length; | |
| 128 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (to_copy > 0) |
| 129 | { | ||
| 130 | 2 | memcpy(buf, data->resp->body + data->resp_sent, to_copy); | |
| 131 | 2 | data->resp_sent += to_copy; | |
| 132 | } | ||
| 133 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (data->resp_sent >= data->resp->body_len) |
| 134 | { | ||
| 135 | 3 | *data_flags = NGHTTP2_DATA_FLAG_EOF; | |
| 136 | } | ||
| 137 | 3 | return to_copy; | |
| 138 | } | ||
| 139 | |||
| 140 | /* Callback invoked when a complete frame is received */ | ||
| 141 | 6 | static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) | |
| 142 | { | ||
| 143 | 6 | H2IO *io = (H2IO *)user_data; | |
| 144 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | ServerConfig *config = io ? io->config : NULL; |
| 145 | H2_LOG("on_frame_recv_callback: start"); | ||
| 146 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (frame->hd.type == NGHTTP2_HEADERS && |
| 147 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | frame->headers.cat == NGHTTP2_HCAT_REQUEST && |
| 148 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)) |
| 149 | { | ||
| 150 | 3 | StreamData *data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); | |
| 151 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (data) |
| 152 | { | ||
| 153 | char raw_request[BUFFER_SIZE]; | ||
| 154 | 9 | snprintf(raw_request, sizeof(raw_request), "%s %s %s\r\n", | |
| 155 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | data->req.method ? data->req.method : "GET", |
| 156 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | data->req.path ? data->req.path : "/", |
| 157 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | data->req.version ? data->req.version : "HTTP/2"); |
| 158 | 3 | log_message(LOG_LEVEL_INFO, "Routing HTTP/2 request for path (synthesized): %s", | |
| 159 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | data->req.path ? data->req.path : "/"); |
| 160 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (!data->resp) |
| 161 | 3 | data->resp = calloc(1, sizeof(Http2Response)); | |
| 162 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!data->resp) |
| 163 | { | ||
| 164 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to allocate Http2Response"); | |
| 165 | ✗ | return 0; | |
| 166 | } | ||
| 167 | 3 | data->resp_sent = 0; | |
| 168 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (route_request_tls(&data->req, raw_request, strlen(raw_request), config, NULL, data->resp) != 0 && |
| 169 | ✗ | data->resp->status_code == 0) { | |
| 170 | ✗ | data->resp->status_code = 500; | |
| 171 | ✗ | snprintf(data->resp->status_text, sizeof(data->resp->status_text), "Internal Server Error"); | |
| 172 | ✗ | data->resp->content_type[0] = '\0'; | |
| 173 | ✗ | data->resp->body[0] = '\0'; | |
| 174 | ✗ | data->resp->body_len = 0; | |
| 175 | } | ||
| 176 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (data->resp->status_code == 0) |
| 177 | ✗ | data->resp->status_code = 200; | |
| 178 | 3 | snprintf(data->resp->status_code_str, sizeof(data->resp->status_code_str), | |
| 179 | 3 | "%d", data->resp->status_code); | |
| 180 | 3 | snprintf(data->resp->content_length_str, sizeof(data->resp->content_length_str), | |
| 181 | 3 | "%zu", data->resp->body_len); | |
| 182 | 3 | data->resp->headers[0] = MAKE_NV(":status", data->resp->status_code_str); | |
| 183 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | data->resp->headers[1] = MAKE_NV("content-type", |
| 184 | data->resp->content_type[0] ? data->resp->content_type : "text/plain"); | ||
| 185 | 3 | data->resp->headers[2] = MAKE_NV("content-length", data->resp->content_length_str); | |
| 186 | 3 | data->resp->num_headers = 3; | |
| 187 | nghttp2_data_provider data_prd; | ||
| 188 | 3 | data_prd.source.ptr = data; | |
| 189 | 3 | data_prd.read_callback = http2_body_read_callback; | |
| 190 | 3 | int rv = nghttp2_submit_response(session, frame->hd.stream_id, | |
| 191 | 3 | data->resp->headers, data->resp->num_headers, | |
| 192 | &data_prd); | ||
| 193 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (rv != 0) |
| 194 | ✗ | log_message(LOG_LEVEL_ERROR, "nghttp2_submit_response failed: %s", nghttp2_strerror(rv)); | |
| 195 | else | ||
| 196 | 3 | log_message(LOG_LEVEL_INFO, "nghttp2_submit_response succeeded for stream %d", frame->hd.stream_id); | |
| 197 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (rv == 0) |
| 198 | { | ||
| 199 | 3 | int send_rv = nghttp2_session_send(session); | |
| 200 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3 | if (send_rv < 0 && send_rv != NGHTTP2_ERR_WOULDBLOCK) |
| 201 | ✗ | log_message(LOG_LEVEL_ERROR, "nghttp2_session_send failed: %s", nghttp2_strerror(send_rv)); | |
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | 6 | return 0; | |
| 206 | } | ||
| 207 | |||
| 208 | /* Callback invoked when a stream is closed */ | ||
| 209 | 3 | static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, | |
| 210 | uint32_t error_code, void *user_data) | ||
| 211 | { | ||
| 212 | (void)error_code; | ||
| 213 | (void)user_data; | ||
| 214 | 3 | StreamData *data = nghttp2_session_get_stream_user_data(session, stream_id); | |
| 215 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (data) |
| 216 | { | ||
| 217 | 3 | free((void *)data->req.method); | |
| 218 | 3 | free((void *)data->req.path); | |
| 219 | 3 | free((void *)data->req.version); | |
| 220 | 3 | free(data->resp); | |
| 221 | 3 | free(data); | |
| 222 | 3 | nghttp2_session_set_stream_user_data(session, stream_id, NULL); | |
| 223 | } | ||
| 224 | 3 | return 0; | |
| 225 | } | ||
| 226 | |||
| 227 | SSL_CTX *ssl_ctx = NULL; | ||
| 228 | static struct io_uring global_ring; // Global io_uring instance for the accept loop. | ||
| 229 | static volatile sig_atomic_t g_shutdown = 0; | ||
| 230 | static int g_server_fd = -1; | ||
| 231 | |||
| 232 | ✗ | static void log_io_uring_error(const char *context, int ret) | |
| 233 | { | ||
| 234 | ✗ | log_message(LOG_LEVEL_ERROR, "%s: %s", context, strerror(-ret)); | |
| 235 | ✗ | } | |
| 236 | |||
| 237 | 8 | static void handle_signal(int sig) | |
| 238 | { | ||
| 239 | (void)sig; | ||
| 240 | 8 | g_shutdown = 1; | |
| 241 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (g_server_fd >= 0) |
| 242 | 8 | close(g_server_fd); | |
| 243 | 8 | } | |
| 244 | |||
| 245 | static void handle_http2_connection(SSL *ssl, int client_fd, ServerConfig *config, struct io_uring *ring); | ||
| 246 | static void handle_http1_connection(SSL *ssl, int client_fd, ServerConfig *config); | ||
| 247 | |||
| 248 | 3 | static void log_hex_prefix(const uint8_t *data, size_t len) | |
| 249 | { | ||
| 250 | char hexbuf[256]; | ||
| 251 | 3 | size_t max = len > 32 ? 32 : len; | |
| 252 | 3 | size_t off = 0; | |
| 253 |
3/4✓ Branch 0 taken 72 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
|
75 | for (size_t i = 0; i < max && off + 3 < sizeof(hexbuf); i++) |
| 254 | { | ||
| 255 | 72 | off += snprintf(hexbuf + off, sizeof(hexbuf) - off, "%02x ", data[i]); | |
| 256 | } | ||
| 257 | 3 | hexbuf[off] = '\0'; | |
| 258 | H2_LOG("h2 recv prefix (%zu bytes): %s", max, hexbuf); | ||
| 259 | 3 | } | |
| 260 | |||
| 261 | /* Callback for nghttp2 to send data via SSL */ | ||
| 262 | 12 | static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, | |
| 263 | size_t length, int flags, void *user_data) | ||
| 264 | { | ||
| 265 | (void)session; | ||
| 266 | (void)flags; | ||
| 267 | 12 | H2IO *io = (H2IO *)user_data; | |
| 268 | 12 | io->want_read = 0; | |
| 269 | 12 | io->want_write = 0; | |
| 270 | |||
| 271 | 12 | ssize_t ret = SSL_write(io->ssl, data, length); | |
| 272 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (ret > 0) |
| 273 | { | ||
| 274 | H2_LOG("h2 send_callback wrote=%zd", ret); | ||
| 275 | 12 | return ret; | |
| 276 | } | ||
| 277 | |||
| 278 | ✗ | int ssl_error = SSL_get_error(io->ssl, ret); | |
| 279 | ✗ | if (ssl_error == SSL_ERROR_WANT_READ) { | |
| 280 | ✗ | io->want_read = 1; | |
| 281 | H2_LOG("h2 send_callback WANT_READ"); | ||
| 282 | ✗ | return NGHTTP2_ERR_WOULDBLOCK; | |
| 283 | } | ||
| 284 | ✗ | if (ssl_error == SSL_ERROR_WANT_WRITE) { | |
| 285 | ✗ | io->want_write = 1; | |
| 286 | H2_LOG("h2 send_callback WANT_WRITE"); | ||
| 287 | ✗ | return NGHTTP2_ERR_WOULDBLOCK; | |
| 288 | } | ||
| 289 | ✗ | log_message(LOG_LEVEL_ERROR, "SSL_write failed in send_callback. Error: %d", ssl_error); | |
| 290 | ✗ | return NGHTTP2_ERR_CALLBACK_FAILURE; | |
| 291 | } | ||
| 292 | |||
| 293 | /* Callback for nghttp2 to receive data via SSL */ | ||
| 294 | 15 | static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, | |
| 295 | int flags, void *user_data) | ||
| 296 | { | ||
| 297 | H2_LOG("recv_callback: start"); | ||
| 298 | (void)session; | ||
| 299 | (void)flags; | ||
| 300 | 15 | H2IO *io = (H2IO *)user_data; | |
| 301 | 15 | io->want_read = 0; | |
| 302 | 15 | io->want_write = 0; | |
| 303 | |||
| 304 | 15 | ssize_t ret = SSL_read(io->ssl, buf, length); | |
| 305 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 9 times.
|
15 | if (ret <= 0) |
| 306 | { | ||
| 307 | 6 | int ssl_error = SSL_get_error(io->ssl, ret); | |
| 308 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (ssl_error == SSL_ERROR_ZERO_RETURN) |
| 309 | { | ||
| 310 | 3 | log_message(LOG_LEVEL_INFO, "SSL connection closed by peer"); | |
| 311 | 3 | return NGHTTP2_ERR_EOF; | |
| 312 | } | ||
| 313 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (ssl_error == SSL_ERROR_WANT_READ) { |
| 314 | 3 | io->want_read = 1; | |
| 315 | H2_LOG("h2 recv_callback WANT_READ"); | ||
| 316 | 3 | return NGHTTP2_ERR_WOULDBLOCK; | |
| 317 | } | ||
| 318 | ✗ | if (ssl_error == SSL_ERROR_WANT_WRITE) { | |
| 319 | ✗ | io->want_write = 1; | |
| 320 | H2_LOG("h2 recv_callback WANT_WRITE"); | ||
| 321 | ✗ | return NGHTTP2_ERR_WOULDBLOCK; | |
| 322 | } | ||
| 323 | ✗ | log_message(LOG_LEVEL_ERROR, "SSL_read failed in recv_callback. Error: %d", ssl_error); | |
| 324 | ✗ | return NGHTTP2_ERR_CALLBACK_FAILURE; | |
| 325 | } | ||
| 326 | 9 | io->total_read += (size_t)ret; | |
| 327 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
|
9 | if (!io->logged_preface) |
| 328 | { | ||
| 329 | 3 | log_hex_prefix(buf, (size_t)ret); | |
| 330 | 3 | io->logged_preface = 1; | |
| 331 | } | ||
| 332 | H2_LOG("recv_callback: end, bytes read: %zd", ret); | ||
| 333 | 9 | return ret; | |
| 334 | } | ||
| 335 | |||
| 336 | /* Nonblocking TLS handshake using thread-local io_uring with instrumentation */ | ||
| 337 | 8 | static int perform_nonblocking_ssl_accept(SSL *ssl, int client_fd, struct io_uring *ring) | |
| 338 | { | ||
| 339 | int ret; | ||
| 340 | struct timeval start, end; | ||
| 341 | 8 | gettimeofday(&start, NULL); | |
| 342 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | while ((ret = SSL_accept(ssl)) <= 0) |
| 343 | { | ||
| 344 | ✗ | int err = SSL_get_error(ssl, ret); | |
| 345 | ✗ | if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) | |
| 346 | ✗ | { | |
| 347 | ✗ | int poll_flags = (err == SSL_ERROR_WANT_READ) ? POLLIN : POLLOUT; | |
| 348 | ✗ | struct io_uring_sqe *sqe = io_uring_get_sqe(ring); | |
| 349 | ✗ | if (!sqe) | |
| 350 | { | ||
| 351 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to get SQE during handshake"); | |
| 352 | ✗ | return -1; | |
| 353 | } | ||
| 354 | ✗ | io_uring_prep_poll_add(sqe, client_fd, poll_flags); | |
| 355 | ✗ | int submit_ret = io_uring_submit(ring); | |
| 356 | ✗ | if (submit_ret < 0) | |
| 357 | { | ||
| 358 | ✗ | log_io_uring_error("io_uring_submit failed during handshake", submit_ret); | |
| 359 | ✗ | return -1; | |
| 360 | } | ||
| 361 | struct io_uring_cqe *cqe; | ||
| 362 | ✗ | int wait_ret = io_uring_wait_cqe(ring, &cqe); | |
| 363 | ✗ | if (wait_ret < 0) | |
| 364 | { | ||
| 365 | ✗ | log_io_uring_error("io_uring_wait_cqe failed during handshake", wait_ret); | |
| 366 | ✗ | return -1; | |
| 367 | } | ||
| 368 | ✗ | io_uring_cqe_seen(ring, cqe); | |
| 369 | } | ||
| 370 | else | ||
| 371 | { | ||
| 372 | ✗ | log_message(LOG_LEVEL_ERROR, "SSL_accept failed nonblockingly, error: %d", err); | |
| 373 | ✗ | return -1; | |
| 374 | } | ||
| 375 | } | ||
| 376 | 8 | gettimeofday(&end, NULL); | |
| 377 | 8 | long handshake_ms = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000; | |
| 378 | 8 | log_message(LOG_LEVEL_INFO, "Nonblocking SSL handshake completed in %ld ms", handshake_ms); | |
| 379 | 8 | return ret; | |
| 380 | } | ||
| 381 | |||
| 382 | /* HTTP/2 connection handler using thread-local io_uring */ | ||
| 383 | 3 | static void handle_http2_connection(SSL *ssl, int client_fd, ServerConfig *config, struct io_uring *ring) | |
| 384 | { | ||
| 385 | (void)ring; | ||
| 386 | 3 | int flags = fcntl(client_fd, F_GETFL, 0); | |
| 387 | 3 | fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); | |
| 388 | nghttp2_session_callbacks *callbacks; | ||
| 389 | nghttp2_session *session; | ||
| 390 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (nghttp2_session_callbacks_new(&callbacks) != 0) |
| 391 | { | ||
| 392 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to initialize HTTP/2 callbacks"); | |
| 393 | ✗ | return; | |
| 394 | } | ||
| 395 | 3 | nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); | |
| 396 | 3 | nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback); | |
| 397 | 3 | nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); | |
| 398 | 3 | nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); | |
| 399 | 3 | nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); | |
| 400 | 3 | H2IO io = { | |
| 401 | .ssl = ssl, | ||
| 402 | .config = config, | ||
| 403 | .want_read = 0, | ||
| 404 | .want_write = 0, | ||
| 405 | .total_read = 0, | ||
| 406 | .logged_preface = 0, | ||
| 407 | }; | ||
| 408 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (nghttp2_session_server_new(&session, callbacks, &io) != 0) |
| 409 | { | ||
| 410 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to create nghttp2 session"); | |
| 411 | ✗ | nghttp2_session_callbacks_del(callbacks); | |
| 412 | ✗ | return; | |
| 413 | } | ||
| 414 | 3 | log_message(LOG_LEVEL_INFO, "HTTP/2 session started"); | |
| 415 | 3 | int rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, NULL, 0); | |
| 416 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (rv < 0) |
| 417 | { | ||
| 418 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to send initial SETTINGS frame: %s", nghttp2_strerror(rv)); | |
| 419 | ✗ | nghttp2_session_del(session); | |
| 420 | ✗ | nghttp2_session_callbacks_del(callbacks); | |
| 421 | ✗ | return; | |
| 422 | } | ||
| 423 | 3 | rv = nghttp2_session_send(session); | |
| 424 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3 | if (rv < 0 && rv != NGHTTP2_ERR_WOULDBLOCK) |
| 425 | { | ||
| 426 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to flush initial SETTINGS: %s", nghttp2_strerror(rv)); | |
| 427 | ✗ | nghttp2_session_del(session); | |
| 428 | ✗ | nghttp2_session_callbacks_del(callbacks); | |
| 429 | ✗ | return; | |
| 430 | } | ||
| 431 | 3 | rv = 0; | |
| 432 |
1/4✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
6 | while (nghttp2_session_want_read(session) || nghttp2_session_want_write(session)) |
| 433 | { | ||
| 434 | 6 | short events = 0; | |
| 435 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
6 | if (io.want_read || io.want_write) |
| 436 | { | ||
| 437 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (io.want_read) |
| 438 | 3 | events |= POLLIN; | |
| 439 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (io.want_write) |
| 440 | ✗ | events |= POLLOUT; | |
| 441 | } | ||
| 442 | else | ||
| 443 | { | ||
| 444 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | if (nghttp2_session_want_read(session)) |
| 445 | 3 | events |= POLLIN; | |
| 446 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if (nghttp2_session_want_write(session)) |
| 447 | ✗ | events |= POLLOUT; | |
| 448 | } | ||
| 449 | |||
| 450 | H2_LOG("h2 loop: want_read=%d want_write=%d io.want_read=%d io.want_write=%d events=0x%x", | ||
| 451 | nghttp2_session_want_read(session), nghttp2_session_want_write(session), | ||
| 452 | io.want_read, io.want_write, events); | ||
| 453 | 6 | struct pollfd pfd = {.fd = client_fd, .events = events}; | |
| 454 | 6 | int pret = poll(&pfd, 1, 1000); | |
| 455 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (pret < 0) |
| 456 | { | ||
| 457 | ✗ | log_message(LOG_LEVEL_ERROR, "poll failed: %s", strerror(errno)); | |
| 458 | 3 | break; | |
| 459 | } | ||
| 460 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (pret == 0) |
| 461 | ✗ | continue; | |
| 462 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) |
| 463 | { | ||
| 464 | ✗ | log_message(LOG_LEVEL_ERROR, "poll error/hangup: revents=%d", pfd.revents); | |
| 465 | ✗ | break; | |
| 466 | } | ||
| 467 | H2_LOG("h2 loop: revents=0x%x", pfd.revents); | ||
| 468 | |||
| 469 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (pfd.revents & POLLIN) |
| 470 | { | ||
| 471 | 6 | rv = nghttp2_session_recv(session); | |
| 472 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (rv < 0) |
| 473 | { | ||
| 474 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (rv == NGHTTP2_ERR_EOF) |
| 475 | 3 | log_message(LOG_LEVEL_INFO, "HTTP/2 session closed by client"); | |
| 476 | ✗ | else if (rv != NGHTTP2_ERR_WOULDBLOCK) | |
| 477 | ✗ | log_message(LOG_LEVEL_ERROR, "nghttp2_session_recv error: %s", nghttp2_strerror(rv)); | |
| 478 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (rv != NGHTTP2_ERR_WOULDBLOCK) |
| 479 | 3 | break; | |
| 480 | } | ||
| 481 | else | ||
| 482 | { | ||
| 483 | H2_LOG("h2 recv processed rv=%d total_read=%zu", rv, io.total_read); | ||
| 484 | } | ||
| 485 | } | ||
| 486 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (pfd.revents & POLLOUT) |
| 487 | { | ||
| 488 | ✗ | rv = nghttp2_session_send(session); | |
| 489 | ✗ | if (rv < 0) | |
| 490 | { | ||
| 491 | ✗ | if (rv != NGHTTP2_ERR_WOULDBLOCK) | |
| 492 | { | ||
| 493 | ✗ | log_message(LOG_LEVEL_ERROR, "nghttp2_session_send error: %s", nghttp2_strerror(rv)); | |
| 494 | ✗ | break; | |
| 495 | } | ||
| 496 | } | ||
| 497 | else | ||
| 498 | { | ||
| 499 | H2_LOG("h2 send processed rv=%d", rv); | ||
| 500 | } | ||
| 501 | } | ||
| 502 | } | ||
| 503 | 3 | nghttp2_session_del(session); | |
| 504 | 3 | nghttp2_session_callbacks_del(callbacks); | |
| 505 | } | ||
| 506 | |||
| 507 | /* HTTP/1.1 connection handler using legacy methods */ | ||
| 508 | 5 | static void handle_http1_connection(SSL *ssl, int client_fd, ServerConfig *config) | |
| 509 | { | ||
| 510 | (void)client_fd; | ||
| 511 | |||
| 512 | // Serve multiple HTTP/1.x requests on the same TLS socket | ||
| 513 | 3 | for (;;) { | |
| 514 | char buffer[BUFFER_SIZE]; | ||
| 515 | 8 | int total_read = 0; | |
| 516 | |||
| 517 | // 1) Read up to the end of headers (\r\n\r\n) | ||
| 518 | 8 | int header_end = -1; | |
| 519 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
|
9 | while (total_read < BUFFER_SIZE - 1) { |
| 520 | 8 | int n = SSL_read(ssl, buffer + total_read, | |
| 521 | BUFFER_SIZE - 1 - total_read); | ||
| 522 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | if (n <= 0) { |
| 523 | // client closed connection or SSL error | ||
| 524 | 5 | goto shutdown_and_close; | |
| 525 | } | ||
| 526 | 6 | total_read += n; | |
| 527 | 6 | header_end = find_header_end(buffer, (size_t)total_read); | |
| 528 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
|
6 | if (header_end >= 0) |
| 529 | 5 | break; | |
| 530 | } | ||
| 531 | 6 | buffer[total_read] = '\0'; | |
| 532 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | if (header_end < 0) { |
| 533 | 1 | const char *too_large = | |
| 534 | "HTTP/1.1 431 Request Header Fields Too Large\r\n" | ||
| 535 | "Content-Length: 0\r\n" | ||
| 536 | "\r\n"; | ||
| 537 | 1 | SSL_write(ssl, too_large, strlen(too_large)); | |
| 538 | 1 | goto shutdown_and_close; | |
| 539 | } | ||
| 540 | |||
| 541 | // 2) Parse the request | ||
| 542 | HttpRequest req; | ||
| 543 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
|
5 | if (parse_http_request(buffer, total_read, &req) != 0) { |
| 544 | 2 | const char *bad_response = | |
| 545 | "HTTP/1.1 400 Bad Request\r\n" | ||
| 546 | "Content-Length: 0\r\n" | ||
| 547 | "\r\n"; | ||
| 548 | 2 | SSL_write(ssl, bad_response, strlen(bad_response)); | |
| 549 | // malformed request → close connection | ||
| 550 | 2 | goto shutdown_and_close; | |
| 551 | } | ||
| 552 | |||
| 553 | 3 | log_message(LOG_LEVEL_INFO, "Valid HTTP request received. Routing..."); | |
| 554 | |||
| 555 | // 3) Route & send response (static, proxy, etc.) | ||
| 556 | 3 | route_request_tls(&req, buffer, total_read, config, ssl, NULL); | |
| 557 | |||
| 558 | // 4) Loop back to read the next request | ||
| 559 | // (do NOT shutdown/close here) | ||
| 560 | } | ||
| 561 | |||
| 562 | 5 | shutdown_and_close: | |
| 563 | 5 | SSL_shutdown(ssl); | |
| 564 | 5 | close(client_fd); | |
| 565 | 5 | } | |
| 566 | |||
| 567 | /* Worker task: uses thread-local io_uring for per-connection I/O */ | ||
| 568 | 8 | void client_task(void *arg) | |
| 569 | { | ||
| 570 | 8 | ClientTaskData *data = (ClientTaskData *)arg; | |
| 571 | static __thread struct io_uring *local_ring = NULL; | ||
| 572 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (!local_ring) |
| 573 | { | ||
| 574 | 8 | local_ring = malloc(sizeof(struct io_uring)); | |
| 575 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (io_uring_queue_init(QUEUE_DEPTH, local_ring, 0) < 0) |
| 576 | { | ||
| 577 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to initialize thread-local io_uring"); | |
| 578 | ✗ | free(local_ring); | |
| 579 | ✗ | local_ring = NULL; | |
| 580 | } | ||
| 581 | } | ||
| 582 | 8 | handle_client(data->client_fd, data->config, local_ring); | |
| 583 | 8 | free(data); | |
| 584 | 8 | } | |
| 585 | |||
| 586 | /* Main per-connection handler; performs nonblocking TLS handshake before dispatching via ALPN */ | ||
| 587 | 8 | void handle_client(int client_fd, ServerConfig *config, struct io_uring *ring) | |
| 588 | { | ||
| 589 | struct timeval timeout; | ||
| 590 | 8 | timeout.tv_sec = 5; | |
| 591 | 8 | timeout.tv_usec = 0; | |
| 592 | 8 | setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); | |
| 593 | 8 | setsockopt(client_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); | |
| 594 | 8 | SSL *ssl = SSL_new(ssl_ctx); | |
| 595 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!ssl) |
| 596 | { | ||
| 597 | ✗ | close(client_fd); | |
| 598 | ✗ | return; | |
| 599 | } | ||
| 600 | 8 | SSL_set_fd(ssl, client_fd); | |
| 601 | 8 | SSL_set_app_data(ssl, config); | |
| 602 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (perform_nonblocking_ssl_accept(ssl, client_fd, ring) <= 0) |
| 603 | { | ||
| 604 | ✗ | log_message(LOG_LEVEL_ERROR, "Nonblocking SSL handshake failed"); | |
| 605 | ✗ | SSL_free(ssl); | |
| 606 | ✗ | close(client_fd); | |
| 607 | ✗ | return; | |
| 608 | } | ||
| 609 | 8 | const unsigned char *alpn_proto = NULL; | |
| 610 | 8 | unsigned int alpn_len = 0; | |
| 611 | 8 | SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_len); | |
| 612 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
8 | if (alpn_len == 2 && memcmp(alpn_proto, "h2", 2) == 0) |
| 613 | { | ||
| 614 | 3 | log_message(LOG_LEVEL_INFO, "Negotiated HTTP/2"); | |
| 615 | 3 | handle_http2_connection(ssl, client_fd, config, ring); | |
| 616 | } | ||
| 617 | else | ||
| 618 | { | ||
| 619 | 5 | log_message(LOG_LEVEL_INFO, "Negotiated HTTP/1.1"); | |
| 620 | 5 | handle_http1_connection(ssl, client_fd, config); | |
| 621 | } | ||
| 622 | 8 | SSL_shutdown(ssl); | |
| 623 | 8 | SSL_free(ssl); | |
| 624 | 8 | close(client_fd); | |
| 625 | } | ||
| 626 | |||
| 627 | /* Main server accept loop using a global io_uring instance */ | ||
| 628 | 8 | int start_server(ServerConfig *config) | |
| 629 | { | ||
| 630 | int server_fd, client_fd; | ||
| 631 | struct sockaddr_in server_addr, client_addr; | ||
| 632 | int ring_ret; | ||
| 633 | size_t max_threads; | ||
| 634 | size_t initial_threads; | ||
| 635 | |||
| 636 | 8 | g_shutdown = 0; | |
| 637 | 8 | memset(&server_addr, 0, sizeof(server_addr)); | |
| 638 | 8 | memset(&client_addr, 0, sizeof(client_addr)); | |
| 639 | |||
| 640 | 8 | ring_ret = io_uring_queue_init(QUEUE_DEPTH * 2, &global_ring, 0); | |
| 641 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (ring_ret != 0) |
| 642 | { | ||
| 643 | ✗ | fprintf(stderr, "global io_uring_queue_init failed: %s\n", strerror(-ring_ret)); | |
| 644 | ✗ | return 1; | |
| 645 | } | ||
| 646 | 8 | server_fd = socket(AF_INET, SOCK_STREAM, 0); | |
| 647 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (server_fd == -1) |
| 648 | { | ||
| 649 | ✗ | perror("Socket creation error"); | |
| 650 | ✗ | io_uring_queue_exit(&global_ring); | |
| 651 | ✗ | return 1; | |
| 652 | } | ||
| 653 | 8 | int enable = 1; | |
| 654 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1) |
| 655 | { | ||
| 656 | ✗ | perror("setsockopt(SO_REUSEADDR) failed"); | |
| 657 | ✗ | close(server_fd); | |
| 658 | ✗ | io_uring_queue_exit(&global_ring); | |
| 659 | ✗ | return 1; | |
| 660 | } | ||
| 661 | 8 | g_server_fd = server_fd; | |
| 662 | 8 | signal(SIGTERM, handle_signal); | |
| 663 | 8 | signal(SIGINT, handle_signal); | |
| 664 | 8 | server_addr.sin_family = AF_INET; | |
| 665 | 8 | server_addr.sin_addr.s_addr = INADDR_ANY; | |
| 666 | 8 | server_addr.sin_port = htons(config->port); | |
| 667 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) |
| 668 | { | ||
| 669 | ✗ | perror("Bind error"); | |
| 670 | ✗ | close(server_fd); | |
| 671 | ✗ | io_uring_queue_exit(&global_ring); | |
| 672 | ✗ | return 1; | |
| 673 | } | ||
| 674 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (listen(server_fd, 2048) == -1) |
| 675 | { | ||
| 676 | ✗ | perror("Listen error"); | |
| 677 | ✗ | close(server_fd); | |
| 678 | ✗ | io_uring_queue_exit(&global_ring); | |
| 679 | ✗ | return 1; | |
| 680 | } | ||
| 681 | 8 | ssl_ctx = create_ssl_context(config->ssl.certificate, config->ssl.private_key); | |
| 682 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!ssl_ctx) |
| 683 | { | ||
| 684 | ✗ | close(server_fd); | |
| 685 | ✗ | io_uring_queue_exit(&global_ring); | |
| 686 | ✗ | return 1; | |
| 687 | } | ||
| 688 | |||
| 689 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | max_threads = config->max_connections > 0 ? (size_t)config->max_connections : 32; |
| 690 | 8 | initial_threads = max_threads < 32 ? max_threads : 32; | |
| 691 | |||
| 692 | 8 | ThreadPool *pool = thread_pool_create(initial_threads, max_threads); | |
| 693 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!pool) |
| 694 | { | ||
| 695 | ✗ | fprintf(stderr, "Failed to create thread pool\n"); | |
| 696 | ✗ | close(server_fd); | |
| 697 | ✗ | io_uring_queue_exit(&global_ring); | |
| 698 | ✗ | cleanup_ssl_context(ssl_ctx); | |
| 699 | ✗ | return 1; | |
| 700 | } | ||
| 701 | 8 | printf("Emme listening on port %d...\n", config->port); | |
| 702 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | while (!g_shutdown) |
| 703 | { | ||
| 704 | 16 | struct io_uring_sqe *sqe = io_uring_get_sqe(&global_ring); | |
| 705 | 16 | socklen_t client_len = sizeof(client_addr); | |
| 706 | |||
| 707 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (!sqe) |
| 708 | { | ||
| 709 | ✗ | fprintf(stderr, "Failed to get SQE for accept\n"); | |
| 710 | 8 | break; | |
| 711 | } | ||
| 712 | 16 | io_uring_prep_accept(sqe, server_fd, (struct sockaddr *)&client_addr, &client_len, 0); | |
| 713 | 16 | int submit_ret = io_uring_submit(&global_ring); | |
| 714 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (submit_ret < 0) |
| 715 | { | ||
| 716 | ✗ | fprintf(stderr, "io_uring_submit (accept) failed: %s\n", strerror(-submit_ret)); | |
| 717 | ✗ | break; | |
| 718 | } | ||
| 719 | struct io_uring_cqe *cqe; | ||
| 720 | 16 | int wait_ret = io_uring_wait_cqe(&global_ring, &cqe); | |
| 721 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
|
16 | if (wait_ret < 0) |
| 722 | { | ||
| 723 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (g_shutdown && |
| 724 | ✗ | (-wait_ret == EINTR || -wait_ret == EBADF || -wait_ret == ENXIO)) | |
| 725 | break; | ||
| 726 | ✗ | fprintf(stderr, "io_uring_wait_cqe (accept) failed: %s\n", strerror(-wait_ret)); | |
| 727 | ✗ | break; | |
| 728 | } | ||
| 729 | 8 | client_fd = cqe->res; | |
| 730 | 8 | io_uring_cqe_seen(&global_ring, cqe); | |
| 731 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (g_shutdown) |
| 732 | { | ||
| 733 | ✗ | if (client_fd >= 0) | |
| 734 | ✗ | close(client_fd); | |
| 735 | ✗ | break; | |
| 736 | } | ||
| 737 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (client_fd < 0) |
| 738 | { | ||
| 739 | ✗ | if (g_shutdown) | |
| 740 | ✗ | break; | |
| 741 | ✗ | continue; | |
| 742 | } | ||
| 743 | 8 | int flags = fcntl(client_fd, F_GETFL, 0); | |
| 744 | 8 | fcntl(client_fd, F_SETFL, flags & ~O_NONBLOCK); | |
| 745 | 8 | ClientTaskData *task_data = malloc(sizeof(ClientTaskData)); | |
| 746 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!task_data) |
| 747 | { | ||
| 748 | ✗ | perror("Failed to allocate memory for client task"); | |
| 749 | ✗ | close(client_fd); | |
| 750 | ✗ | continue; | |
| 751 | } | ||
| 752 | 8 | task_data->client_fd = client_fd; | |
| 753 | 8 | task_data->config = config; | |
| 754 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (!thread_pool_add_task(pool, client_task, task_data)) |
| 755 | { | ||
| 756 | ✗ | fprintf(stderr, "Failed to add task to thread pool\n"); | |
| 757 | ✗ | free(task_data); | |
| 758 | ✗ | close(client_fd); | |
| 759 | } | ||
| 760 | } | ||
| 761 | 8 | thread_pool_destroy(pool); | |
| 762 | 8 | close(server_fd); | |
| 763 | 8 | g_server_fd = -1; | |
| 764 | 8 | io_uring_queue_exit(&global_ring); | |
| 765 | 8 | cleanup_ssl_context(ssl_ctx); | |
| 766 | 8 | return 0; | |
| 767 | } | ||
| 768 |