Home | History | Annotate | Line # | Download | only in libntp
      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