1 /* $NetBSD: recvbuff.c,v 1.10 2024/08/18 20:47:13 christos Exp $ */
2
3 #ifdef HAVE_CONFIG_H
4 # include <config.h>
5 #endif
6
7 #include <stdio.h>
8
9 #include "ntp_assert.h"
10 #include "ntp_syslog.h"
11 #include "ntp_stdlib.h"
12 #include "ntp_lists.h"
13 #include "recvbuff.h"
14 #include "iosignal.h"
15
16 #if (RECV_INC & (RECV_INC-1))
17 # error RECV_INC not a power of 2!
18 #endif
19 #if (RECV_BATCH & (RECV_BATCH - 1))
20 #error RECV_BATCH not a power of 2!
21 #endif
22 #if (RECV_BATCH < RECV_INC)
23 #error RECV_BATCH must be >= RECV_INC!
24 #endif
25
26 /*
27 * Memory allocation
28 */
29 static u_long volatile full_recvbufs; /* recvbufs on full_recv_fifo */
30 static u_long volatile free_recvbufs; /* recvbufs on free_recv_list */
31 static u_long volatile total_recvbufs; /* total recvbufs currently in use */
32 static u_long volatile lowater_adds; /* number of times we have added memory */
33 static u_long volatile buffer_shortfall;/* number of missed free receive buffers
34 between replenishments */
35 static u_long limit_recvbufs; /* maximum total of receive buffers */
36 static u_long emerg_recvbufs; /* emergency/urgent buffers to keep */
37
38 static DECL_FIFO_ANCHOR(recvbuf_t) full_recv_fifo;
39 static recvbuf_t * free_recv_list;
40
41 #if defined(SYS_WINNT)
42
43 /*
44 * For Windows we need to set up a lock to manipulate the
45 * recv buffers to prevent corruption. We keep it lock for as
46 * short a time as possible
47 */
48 static CRITICAL_SECTION RecvLock;
49 static CRITICAL_SECTION FreeLock;
50 # define LOCK_R() EnterCriticalSection(&RecvLock)
51 # define UNLOCK_R() LeaveCriticalSection(&RecvLock)
52 # define LOCK_F() EnterCriticalSection(&FreeLock)
53 # define UNLOCK_F() LeaveCriticalSection(&FreeLock)
54 #else
55 # define LOCK_R() do {} while (FALSE)
56 # define UNLOCK_R() do {} while (FALSE)
57 # define LOCK_F() do {} while (FALSE)
58 # define UNLOCK_F() do {} while (FALSE)
59 #endif
60
61 #ifdef DEBUG
62 static void uninit_recvbuff(void);
63 #endif
64
65
66 u_long
free_recvbuffs(void)67 free_recvbuffs (void)
68 {
69 return free_recvbufs;
70 }
71
72 u_long
full_recvbuffs(void)73 full_recvbuffs (void)
74 {
75 return full_recvbufs;
76 }
77
78 u_long
total_recvbuffs(void)79 total_recvbuffs (void)
80 {
81 return total_recvbufs;
82 }
83
84 u_long
lowater_additions(void)85 lowater_additions(void)
86 {
87 return lowater_adds;
88 }
89
90 static inline void
initialise_buffer(recvbuf_t * buff)91 initialise_buffer(recvbuf_t *buff)
92 {
93 ZERO(*buff);
94 }
95
96 static void
create_buffers(size_t nbufs)97 create_buffers(
98 size_t nbufs
99 )
100 {
101 static const u_int chunk =
102 # ifndef DEBUG
103 RECV_INC;
104 # else
105 /* Allocate each buffer individually so they can be free()d
106 * during ntpd shutdown on DEBUG builds to keep them out of heap
107 * leak reports.
108 */
109 1;
110 # endif
111 static int/*BOOL*/ doneonce;
112 recvbuf_t * bufp;
113 u_int i;
114 size_t abuf;
115
116 /*[bug 3666]: followup -- reset shortfalls in all cases */
117 abuf = nbufs + buffer_shortfall;
118 buffer_shortfall = 0;
119
120 if (limit_recvbufs <= total_recvbufs) {
121 if (!doneonce) {
122 msyslog(LOG_CRIT, "Unable to allocate receive"
123 " buffer, %lu/%lu",
124 total_recvbufs, limit_recvbufs);
125 doneonce = TRUE;
126 }
127 return;
128 }
129
130 if (abuf < nbufs || abuf > RECV_BATCH) {
131 abuf = RECV_BATCH; /* clamp on overflow */
132 } else {
133 abuf += (~abuf + 1) & (RECV_INC - 1); /* round up */
134 }
135 if (abuf > (limit_recvbufs - total_recvbufs)) {
136 abuf = limit_recvbufs - total_recvbufs;
137 }
138 abuf += (~abuf + 1) & (chunk - 1); /* round up */
139
140 while (abuf) {
141 bufp = calloc(chunk, sizeof(*bufp));
142 if (!bufp) {
143 msyslog(LOG_CRIT, "Out of memory, allocating "
144 "%u recvbufs, %lu bytes",
145 chunk, (u_long)sizeof(*bufp) * chunk);
146 limit_recvbufs = total_recvbufs;
147 break;
148 }
149 for (i = chunk; i; --i,++bufp) {
150 LINK_SLIST(free_recv_list, bufp, link);
151 }
152 free_recvbufs += chunk;
153 total_recvbufs += chunk;
154 abuf -= chunk;
155 }
156 ++lowater_adds;
157 }
158
159 void
init_recvbuff(int nbufs)160 init_recvbuff(int nbufs)
161 {
162
163 /*
164 * Init buffer free list and stat counters
165 */
166 free_recvbufs = total_recvbufs = 0;
167 full_recvbufs = lowater_adds = 0;
168
169 limit_recvbufs = RECV_TOOMANY;
170 emerg_recvbufs = RECV_CLOCK;
171
172 create_buffers(nbufs);
173
174 # if defined(SYS_WINNT)
175 InitializeCriticalSection(&RecvLock);
176 InitializeCriticalSection(&FreeLock);
177 # endif
178
179 # ifdef DEBUG
180 atexit(&uninit_recvbuff);
181 # endif
182 }
183
184
185 #ifdef DEBUG
186 static void
uninit_recvbuff(void)187 uninit_recvbuff(void)
188 {
189 recvbuf_t *rbunlinked;
190
191 for (;;) {
192 UNLINK_FIFO(rbunlinked, full_recv_fifo, link);
193 if (rbunlinked == NULL)
194 break;
195 free(rbunlinked);
196 }
197
198 for (;;) {
199 UNLINK_HEAD_SLIST(rbunlinked, free_recv_list, link);
200 if (rbunlinked == NULL)
201 break;
202 free(rbunlinked);
203 }
204 # if defined(SYS_WINNT)
205 DeleteCriticalSection(&FreeLock);
206 DeleteCriticalSection(&RecvLock);
207 # endif
208 }
209 #endif /* DEBUG */
210
211
212 /*
213 * freerecvbuf - make a single recvbuf available for reuse
214 */
215 void
freerecvbuf(recvbuf_t * rb)216 freerecvbuf(recvbuf_t *rb)
217 {
218 if (rb) {
219 if (--rb->used != 0) {
220 msyslog(LOG_ERR, "******** freerecvbuff non-zero usage: %d *******", rb->used);
221 rb->used = 0;
222 }
223 LOCK_F();
224 LINK_SLIST(free_recv_list, rb, link);
225 ++free_recvbufs;
226 UNLOCK_F();
227 }
228 }
229
230
231 void
add_full_recv_buffer(recvbuf_t * rb)232 add_full_recv_buffer(recvbuf_t *rb)
233 {
234 if (rb == NULL) {
235 msyslog(LOG_ERR, "add_full_recv_buffer received NULL buffer");
236 return;
237 }
238 LOCK_R();
239 LINK_FIFO(full_recv_fifo, rb, link);
240 ++full_recvbufs;
241 UNLOCK_R();
242 }
243
244
245 recvbuf_t *
get_free_recv_buffer(int urgent)246 get_free_recv_buffer(
247 int /*BOOL*/ urgent
248 )
249 {
250 recvbuf_t *buffer = NULL;
251
252 LOCK_F();
253 if (free_recvbufs > (urgent ? 0 : emerg_recvbufs)) {
254 UNLINK_HEAD_SLIST(buffer, free_recv_list, link);
255 }
256
257 if (buffer != NULL) {
258 if (free_recvbufs)
259 --free_recvbufs;
260 initialise_buffer(buffer);
261 ++buffer->used;
262 } else {
263 ++buffer_shortfall;
264 }
265 UNLOCK_F();
266
267 return buffer;
268 }
269
270
271 #ifdef HAVE_IO_COMPLETION_PORT
272 recvbuf_t *
get_free_recv_buffer_alloc(int urgent)273 get_free_recv_buffer_alloc(
274 int /*BOOL*/ urgent
275 )
276 {
277 LOCK_F();
278 if (free_recvbufs <= emerg_recvbufs || buffer_shortfall > 0)
279 create_buffers(RECV_INC);
280 UNLOCK_F();
281 return get_free_recv_buffer(urgent);
282 }
283 #endif
284
285
286 recvbuf_t *
get_full_recv_buffer(void)287 get_full_recv_buffer(void)
288 {
289 recvbuf_t * rbuf;
290
291 /*
292 * make sure there are free buffers when we wander off to do
293 * lengthy packet processing with any buffer we grab from the
294 * full list.
295 *
296 * fixes malloc() interrupted by SIGIO risk (Bug 889)
297 */
298 LOCK_F();
299 if (free_recvbufs <= emerg_recvbufs || buffer_shortfall > 0)
300 create_buffers(RECV_INC);
301 UNLOCK_F();
302
303 /*
304 * try to grab a full buffer
305 */
306 LOCK_R();
307 UNLINK_FIFO(rbuf, full_recv_fifo, link);
308 if (rbuf != NULL && full_recvbufs)
309 --full_recvbufs;
310 UNLOCK_R();
311
312 return rbuf;
313 }
314
315
316 /*
317 * purge_recv_buffers_for_fd() - purges any previously-received input
318 * from a given file descriptor.
319 */
320 void
purge_recv_buffers_for_fd(int fd)321 purge_recv_buffers_for_fd(
322 int fd
323 )
324 {
325 recvbuf_t *rbufp;
326 recvbuf_t *next;
327 recvbuf_t *punlinked;
328 recvbuf_t *freelist = NULL;
329
330 /* We want to hold only one lock at a time. So we do a scan on
331 * the full buffer queue, collecting items as we go, and when
332 * done we spool the the collected items to 'freerecvbuf()'.
333 */
334 LOCK_R();
335
336 for (rbufp = HEAD_FIFO(full_recv_fifo);
337 rbufp != NULL;
338 rbufp = next)
339 {
340 next = rbufp->link;
341 # ifdef HAVE_IO_COMPLETION_PORT
342 if (rbufp->dstadr == NULL && rbufp->fd == fd)
343 # else
344 if (rbufp->fd == fd)
345 # endif
346 {
347 UNLINK_MID_FIFO(punlinked, full_recv_fifo,
348 rbufp, link, recvbuf_t);
349 INSIST(punlinked == rbufp);
350 if (full_recvbufs)
351 --full_recvbufs;
352 rbufp->link = freelist;
353 freelist = rbufp;
354 }
355 }
356
357 UNLOCK_R();
358
359 while (freelist) {
360 next = freelist->link;
361 freerecvbuf(freelist);
362 freelist = next;
363 }
364 }
365
366
367 /*
368 * Checks to see if there are buffers to process
369 */
has_full_recv_buffer(void)370 isc_boolean_t has_full_recv_buffer(void)
371 {
372 if (HEAD_FIFO(full_recv_fifo) != NULL)
373 return (ISC_TRUE);
374 else
375 return (ISC_FALSE);
376 }
377
378
379 #ifdef NTP_DEBUG_LISTS_H
380 void
check_gen_fifo_consistency(void * fifo)381 check_gen_fifo_consistency(void *fifo)
382 {
383 gen_fifo *pf;
384 gen_node *pthis;
385 gen_node **pptail;
386
387 pf = fifo;
388 REQUIRE((NULL == pf->phead && NULL == pf->pptail) ||
389 (NULL != pf->phead && NULL != pf->pptail));
390
391 pptail = &pf->phead;
392 for (pthis = pf->phead;
393 pthis != NULL;
394 pthis = pthis->link)
395 if (NULL != pthis->link)
396 pptail = &pthis->link;
397
398 REQUIRE(NULL == pf->pptail || pptail == pf->pptail);
399 }
400 #endif /* NTP_DEBUG_LISTS_H */
401