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