Skip to content

Commit 3d4eb30

Browse files
danielinuxjaromil
authored andcommitted
Addressed copilot's review comments
1 parent 59eeab8 commit 3d4eb30

5 files changed

Lines changed: 132 additions & 46 deletions

File tree

src/dohd.c

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,25 @@
5050
#define HTTP2_MODE 1 /* Change to '1' once implemented */
5151
#define OCSP_RESPONDER 1
5252

53-
/* Memory pool sizes */
53+
/*
54+
* Memory pool sizes:
55+
*
56+
* MAX_CLIENTS and MAX_REQUESTS define the upper bounds for the internal
57+
* connection and request pools. These limits are used to cap memory usage
58+
* and reduce dynamic allocations under high load.
59+
*
60+
* The default values (1024 clients, 4096 requests) are chosen as a
61+
* reasonable compromise between memory footprint and concurrency for
62+
* typical deployments. If you need to tune these for a specific
63+
* environment (e.g. very small embedded systems or large-scale servers),
64+
* you can adjust the values at compile time.
65+
*/
5466
#define MAX_CLIENTS 1024
5567
#define MAX_REQUESTS 4096
5668

69+
/* DNS request timeout in milliseconds */
70+
#define DNS_REQUEST_TIMEOUT_MS 5000
71+
5772
#define IP6_LOCALHOST { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 }
5873
#define DOHD_REQ_MIN 20
5974
#define STR_HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
@@ -277,9 +292,12 @@ static void dohd_destroy_request(struct req_slot *req);
277292
static mempool_t *client_pool = NULL;
278293
static mempool_t *request_pool = NULL;
279294

280-
/* Client hash table for O(1) lookup by fd */
281-
#define CLIENT_HASH_BITS 10
282-
#define CLIENT_HASH_SIZE (1 << CLIENT_HASH_BITS) /* 1024 buckets */
295+
/*
296+
* Client hash table for O(1) lookup by fd.
297+
* Size is 2x MAX_CLIENTS to maintain low load factor and minimize collisions.
298+
* Using power-of-2 size enables fast modulo via bitmask.
299+
*/
300+
#define CLIENT_HASH_SIZE (MAX_CLIENTS * 2) /* 2x capacity for low collisions */
283301
#define CLIENT_HASH_MASK (CLIENT_HASH_SIZE - 1)
284302

285303
static struct client_data *client_hash_table[CLIENT_HASH_SIZE];
@@ -475,7 +493,7 @@ struct req_slot *dns_create_request_h2(struct client_data *cd, uint32_t stream_i
475493
}
476494
req = mempool_alloc(request_pool);
477495
if (req == NULL) {
478-
dohprint(DOH_ERR, "Failed to allocate memory for a new DNS request.");
496+
dohprint(DOH_ERR, "Request pool exhausted (capacity: %u)", MAX_REQUESTS);
479497
return req;
480498
}
481499
req->resolver = next_resolver();
@@ -488,10 +506,18 @@ struct req_slot *dns_create_request_h2(struct client_data *cd, uint32_t stream_i
488506
req->dns_sd = socket(((struct sockaddr_in *)req->resolver)->sin_family,
489507
SOCK_DGRAM, 0);
490508
if (req->dns_sd < 0) {
491-
dohprint(DOH_ERR, "Failed to create traditional DNS socket to forward the request.");
509+
dohprint(DOH_ERR, "Failed to create UDP socket for DNS request");
492510
mempool_free(request_pool, req);
493511
return NULL;
494512
}
513+
514+
/* Set socket receive timeout to complement timer-based timeout.
515+
* This ensures recv() won't block indefinitely if the event loop
516+
* callback fires but data isn't immediately available. */
517+
struct timeval tv = { .tv_sec = DNS_REQUEST_TIMEOUT_MS / 1000,
518+
.tv_usec = (DNS_REQUEST_TIMEOUT_MS % 1000) * 1000 };
519+
setsockopt(req->dns_sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
520+
495521
/* append to list */
496522
req->next = cd->list;
497523
cd->list = req;
@@ -509,29 +535,31 @@ struct req_slot *dns_create_request_h2(struct client_data *cd, uint32_t stream_i
509535
return req;
510536
}
511537

512-
/* DNS request timeout in milliseconds (5 seconds) */
513-
#define DNS_REQUEST_TIMEOUT_MS 5000
514-
515538
/* Timeout callback - upstream DNS didn't respond in time */
516539
static void dns_request_timeout(void *arg)
517540
{
518541
struct req_slot *req = (struct req_slot *)arg;
519542
struct client_data *cd;
543+
nghttp2_session *session;
520544

521-
dohprint(DOH_WARN, "DNS request timeout (stream %u)", req->h2_stream_id);
522545
DOH_Stats.socket_errors++;
523546

524547
/* Use owner_fd to safely look up if client still exists without
525-
* dereferencing the potentially-freed owner pointer */
548+
* dereferencing the potentially-freed owner pointer.
549+
* Note: This is single-threaded, so no race between lookup and use. */
526550
cd = client_hash_lookup(req->owner_fd);
527-
if (cd && cd == req->owner && cd->h2_session) {
528-
/* Send HTTP 504 Gateway Timeout to client */
529-
nghttp2_nv nva[] = {
530-
MAKE_NV(":status", "504"),
531-
MAKE_NV("server", "dohd"),
532-
};
533-
nghttp2_submit_response(cd->h2_session, req->h2_stream_id, nva, 2, NULL);
534-
nghttp2_session_send(cd->h2_session);
551+
if (cd && cd == req->owner) {
552+
/* Capture session pointer after validation */
553+
session = cd->h2_session;
554+
if (session) {
555+
/* Send HTTP 504 Gateway Timeout to client */
556+
nghttp2_nv nva[] = {
557+
MAKE_NV(":status", "504"),
558+
MAKE_NV("server", "dohd"),
559+
};
560+
nghttp2_submit_response(session, req->h2_stream_id, nva, 2, NULL);
561+
nghttp2_session_send(session);
562+
}
535563
}
536564

537565
/* Timer already fired, clear it before destroy */
@@ -553,7 +581,6 @@ static int dns_send_request_h2(struct req_slot *req)
553581
ret = sendto(req->dns_sd, req->h2_request_buffer, req->h2_request_len, 0,
554582
(struct sockaddr *)req->resolver, req->resolver_sz);
555583
if (ret < 0) {
556-
dohprint(DOH_ERR, "Resolver send failed: %s\n", strerror(errno));
557584
DOH_Stats.socket_errors++;
558585
check_stats();
559586
dohd_destroy_request(req);
@@ -572,17 +599,6 @@ static int dns_send_request_h2(struct req_slot *req)
572599

573600
static ssize_t client_ssl_write(struct client_data *cd, const void *data, size_t len)
574601
{
575-
#ifdef VERBOSE_HTTP_DEBUG
576-
int i;
577-
uint8_t *reply = (uint8_t *)data;
578-
dohprint(DOH_NOTICE,"\n\nSSL Write: %lu bytes\n", len);
579-
for (i = 0; i < len; i++) {
580-
dohprint(DOH_NOTICE,"%02x ", (reply[i]));
581-
if ((i % 16) == 15)
582-
dohprint(DOH_NOTICE,"\n");
583-
}
584-
dohprint(DOH_NOTICE,"\n\n");
585-
#endif
586602
return wolfSSL_write(cd->ssl, data, len);
587603
}
588604

@@ -677,7 +693,6 @@ static uint32_t dnsreply_min_age(const void *p, size_t len)
677693
for (i = 0; i < ntohs(hdr->qdcount); i++) {
678694
skip = dns_skip_question(&record, len);
679695
if (skip < DNSQ_SUFFIX_LEN) {
680-
dohprint(DOH_WARN, "Cannot parse DNS reply!\n");
681696
return min_ttl;
682697
}
683698
len -= skip;
@@ -819,7 +834,6 @@ static void dohd_reply(int fd, short __attribute__((unused)) revents,
819834

820835
len = recv(fd, buff, bufsz, 0);
821836
if (len < 16) {
822-
dohprint(DOH_ERR, "Error while receiving DNS reply: socket error %d\n", errno);
823837
goto destroy;
824838
}
825839

@@ -855,7 +869,6 @@ static void dohd_reply(int fd, short __attribute__((unused)) revents,
855869
data_prd.read_callback = h2_cb_req_submit;
856870
if (nghttp2_submit_response(req->owner->h2_session,
857871
req->h2_stream_id, nva, 4, &data_prd) < 0) {
858-
dohprint(DOH_WARN, "H2: submit response failed\n");
859872
dohd_destroy_request(req);
860873
return;
861874
}
@@ -900,15 +913,12 @@ static int h2_cb_on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
900913
}
901914
req = nghttp2_session_get_stream_user_data(session, stream_id);
902915
if (!req) {
903-
dohprint(DOH_WARN, "H2: received bogus DATA not associated to request\n");
904916
goto data_fail;
905917
}
906918
if (req->owner != cd) {
907-
dohprint(DOH_WARN, "H2: received DATA chunk with wrong stream_id\n");
908919
goto data_fail;
909920
}
910921
if (len > DNS_BUFFER_MAXSIZE) {
911-
dohprint(DOH_WARN, "H2: received DATA chunk too large (%lu)\n", len);
912922
goto data_fail;
913923
}
914924
memcpy(req->h2_request_buffer, data, len);
@@ -1003,6 +1013,14 @@ static int h2_cb_on_header(nghttp2_session *session,
10031013
if (!req) {
10041014
req = dns_create_request_h2(cd, frame->hd.stream_id);
10051015
if (!req) {
1016+
/* Pool exhausted or socket creation failed - send 503 */
1017+
nghttp2_nv nva[] = {
1018+
MAKE_NV(":status", "503"),
1019+
MAKE_NV("server", "dohd"),
1020+
MAKE_NV("retry-after", "1"),
1021+
};
1022+
nghttp2_submit_response(session, frame->hd.stream_id, nva, 3, NULL);
1023+
DOH_Stats.socket_errors++;
10061024
return 0;
10071025
}
10081026
}

src/libevquick.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,15 @@
1717
#include <fcntl.h>
1818
#include <assert.h>
1919

20-
#define EVQUICK_MAX_EVENTS 256
20+
/*
21+
* Maximum events returned per epoll_wait call.
22+
* This bounds the stack-allocated event array and determines batch size.
23+
* Higher values reduce syscall overhead under high load but use more stack.
24+
* 512 balances efficiency with typical ulimit stack sizes.
25+
*/
26+
#ifndef EVQUICK_MAX_EVENTS
27+
#define EVQUICK_MAX_EVENTS 512
28+
#endif
2129

2230

2331

@@ -464,7 +472,7 @@ void evquick_loop(void)
464472
#else
465473
e->err_callback(e->fd, ep_events[i].events, e->arg);
466474
#endif
467-
} else {
475+
} else if (e->callback) {
468476
/* Call normal callback for read/write or if no err_callback */
469477
#ifdef EVQUICK_PTHREAD
470478
e->callback(ctx, e->fd, ep_events[i].events, e->arg);
@@ -474,7 +482,12 @@ void evquick_loop(void)
474482
}
475483
}
476484

477-
/* Free events that were deleted during this batch */
485+
/* Free events that were deleted during this batch.
486+
* Note: Deferred freeing prevents use-after-free when events are
487+
* deleted from within callbacks. If many events are deleted in one
488+
* batch, memory is held until this point. This is acceptable for
489+
* typical workloads but could cause temporary memory spikes under
490+
* extreme conditions. */
478491
while (ctx->pending_free) {
479492
evquick_event *e = ctx->pending_free;
480493
ctx->pending_free = e->next;

src/mempool.c

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*
44
* Uses a freelist for O(1) alloc/free. Each free slot stores
55
* the index of the next free slot, forming a linked list.
6+
* A bitmap tracks allocation status to detect double-free attempts.
67
*/
78

89
#include "mempool.h"
@@ -18,13 +19,30 @@ struct slot_header {
1819

1920
struct mempool {
2021
uint8_t *data; /* Pool memory */
22+
uint8_t *alloc_bitmap; /* Bitmap: 1 = allocated, 0 = free */
2123
size_t slot_size; /* Size of each slot (including header alignment) */
2224
size_t user_size; /* User-requested slot size */
2325
uint32_t capacity; /* Total number of slots */
2426
uint32_t free_head; /* Index of first free slot */
2527
struct mempool_stats stats; /* Statistics */
2628
};
2729

30+
/* Bitmap helpers */
31+
static inline int bitmap_get(uint8_t *bitmap, uint32_t index)
32+
{
33+
return (bitmap[index / 8] >> (index % 8)) & 1;
34+
}
35+
36+
static inline void bitmap_set(uint8_t *bitmap, uint32_t index)
37+
{
38+
bitmap[index / 8] |= (1 << (index % 8));
39+
}
40+
41+
static inline void bitmap_clear(uint8_t *bitmap, uint32_t index)
42+
{
43+
bitmap[index / 8] &= ~(1 << (index % 8));
44+
}
45+
2846
/* Align slot size to at least hold the header and maintain alignment */
2947
static size_t align_slot_size(size_t user_size)
3048
{
@@ -39,6 +57,7 @@ mempool_t *mempool_create(size_t slot_size, uint32_t capacity)
3957
mempool_t *pool;
4058
uint32_t i;
4159
size_t aligned_size;
60+
size_t bitmap_size;
4261

4362
if (slot_size == 0 || capacity == 0)
4463
return NULL;
@@ -54,6 +73,16 @@ mempool_t *mempool_create(size_t slot_size, uint32_t capacity)
5473
return NULL;
5574
}
5675

76+
/* Allocate bitmap: 1 bit per slot, rounded up to bytes */
77+
bitmap_size = (capacity + 7) / 8;
78+
pool->alloc_bitmap = malloc(bitmap_size);
79+
if (!pool->alloc_bitmap) {
80+
free(pool->data);
81+
free(pool);
82+
return NULL;
83+
}
84+
memset(pool->alloc_bitmap, 0, bitmap_size);
85+
5786
pool->slot_size = aligned_size;
5887
pool->user_size = slot_size;
5988
pool->capacity = capacity;
@@ -80,6 +109,7 @@ void mempool_destroy(mempool_t *pool)
80109
{
81110
if (!pool)
82111
return;
112+
free(pool->alloc_bitmap);
83113
free(pool->data);
84114
free(pool);
85115
}
@@ -88,6 +118,7 @@ void *mempool_alloc(mempool_t *pool)
88118
{
89119
struct slot_header *slot;
90120
void *ptr;
121+
uint32_t index;
91122

92123
if (!pool || pool->free_head == SLOT_END) {
93124
if (pool)
@@ -96,9 +127,13 @@ void *mempool_alloc(mempool_t *pool)
96127
}
97128

98129
/* Pop from freelist */
99-
slot = (struct slot_header *)(pool->data + pool->free_head * pool->slot_size);
130+
index = pool->free_head;
131+
slot = (struct slot_header *)(pool->data + index * pool->slot_size);
100132
pool->free_head = slot->next_free;
101133

134+
/* Mark as allocated in bitmap */
135+
bitmap_set(pool->alloc_bitmap, index);
136+
102137
/* Zero the slot for safety */
103138
ptr = (void *)slot;
104139
memset(ptr, 0, pool->user_size);
@@ -132,6 +167,15 @@ void mempool_free(mempool_t *pool, void *ptr)
132167
if ((uint8_t *)ptr != pool->data + index * pool->slot_size)
133168
return; /* Misaligned pointer */
134169

170+
/* Check for double-free using bitmap */
171+
if (!bitmap_get(pool->alloc_bitmap, index)) {
172+
pool->stats.double_free++;
173+
return; /* Already free - ignore to prevent corruption */
174+
}
175+
176+
/* Mark as free in bitmap */
177+
bitmap_clear(pool->alloc_bitmap, index);
178+
135179
/* Push onto freelist */
136180
slot = (struct slot_header *)ptr;
137181
slot->next_free = pool->free_head;
@@ -153,10 +197,15 @@ void mempool_stats(mempool_t *pool, struct mempool_stats *stats)
153197
void mempool_reset(mempool_t *pool)
154198
{
155199
uint32_t i;
200+
size_t bitmap_size;
156201

157202
if (!pool)
158203
return;
159204

205+
/* Clear allocation bitmap */
206+
bitmap_size = (pool->capacity + 7) / 8;
207+
memset(pool->alloc_bitmap, 0, bitmap_size);
208+
160209
/* Rebuild freelist */
161210
for (i = 0; i < pool->capacity - 1; i++) {
162211
struct slot_header *h = (struct slot_header *)(pool->data + i * pool->slot_size);

src/mempool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct mempool_stats {
1919
uint32_t alloc_count; /* Total allocations */
2020
uint32_t free_count; /* Total frees */
2121
uint32_t exhausted; /* Times pool was full */
22+
uint32_t double_free; /* Double-free attempts detected */
2223
};
2324

2425
/* Opaque pool handle */

test/test_mempool.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,15 +425,20 @@ TEST(double_free_safe)
425425

426426
mempool_free(pool, p);
427427

428-
/* Double free - pointer is now part of freelist,
429-
* but freeing again should be handled gracefully.
430-
* The slot is already free, so this is a no-op or
431-
* will corrupt the freelist. We just verify no crash. */
428+
/* Double free - should be detected and ignored via bitmap */
432429
mempool_free(pool, p);
433430

434-
/* Pool should still be usable (though potentially corrupted) */
431+
/* Verify double-free was detected */
432+
struct mempool_stats stats;
433+
mempool_stats(pool, &stats);
434+
ASSERT_EQ(stats.double_free, 1);
435+
ASSERT_EQ(stats.allocated, 0);
436+
ASSERT_EQ(stats.free_count, 1); /* Only first free counted */
437+
438+
/* Pool should still be usable and NOT corrupted */
435439
void *p2 = mempool_alloc(pool);
436440
ASSERT_NOT_NULL(p2);
441+
ASSERT_EQ(p2, p); /* Same slot reused */
437442

438443
mempool_destroy(pool);
439444
}

0 commit comments

Comments
 (0)