xref: /netbsd-src/external/bsd/ntp/dist/libntp/recvbuff.c (revision f8cf1a9151c7af1cb0bd8b09c13c66bca599c027)
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
67 free_recvbuffs (void)
68 {
69 	return free_recvbufs;
70 }
71 
72 u_long
73 full_recvbuffs (void)
74 {
75 	return full_recvbufs;
76 }
77 
78 u_long
79 total_recvbuffs (void)
80 {
81 	return total_recvbufs;
82 }
83 
84 u_long
85 lowater_additions(void)
86 {
87 	return lowater_adds;
88 }
89 
90 static inline void
91 initialise_buffer(recvbuf_t *buff)
92 {
93 	ZERO(*buff);
94 }
95 
96 static void
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
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
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
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
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 *
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 *
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 *
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
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  */
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
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