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