1 /* $NetBSD: ratelimit.c,v 1.2 2021/10/12 22:51:28 rillig Exp $ */
2
3 /*-
4 * Copyright (c) 2021 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: ratelimit.c,v 1.2 2021/10/12 22:51:28 rillig Exp $");
33
34 #include <sys/queue.h>
35
36 #include <arpa/inet.h>
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <syslog.h>
41 #include <unistd.h>
42 #include <string.h>
43 #include <errno.h>
44 #include <stddef.h>
45
46 #include "inetd.h"
47
48 union addr {
49 struct in_addr ipv4_addr;
50 /* ensure aligned for comparison in rl_ipv6_eq (already is on 64-bit) */
51 #ifdef INET6
52 struct in6_addr ipv6_addr __attribute__((aligned(16)));
53 #endif
54 char other_addr[NI_MAXHOST];
55 };
56
57 static void rl_reset(struct servtab *, time_t);
58 static time_t rl_time(void);
59 static void rl_get_name(struct servtab *, int, union addr *);
60 static void rl_drop_connection(struct servtab *, int);
61 static struct rl_ip_node *rl_add(struct servtab *, union addr *);
62 static struct rl_ip_node *rl_try_get_ip(struct servtab *, union addr *);
63 static bool rl_ip_eq(struct servtab *, union addr *, struct rl_ip_node *);
64 #ifdef INET6
65 static bool rl_ipv6_eq(struct in6_addr *, struct in6_addr *);
66 #endif
67 #ifdef DEBUG_ENABLE
68 static void rl_print_found_node(struct servtab *, struct rl_ip_node *);
69 #endif
70 static void rl_log_address_exceed(struct servtab *, struct rl_ip_node *);
71 static const char *rl_node_tostring(struct servtab *, struct rl_ip_node *, char[NI_MAXHOST]);
72 static bool rl_process_service_max(struct servtab *, int, time_t *);
73 static bool rl_process_ip_max(struct servtab *, int, time_t *);
74
75 /* Return 0 on allow, -1 if connection should be blocked */
76 int
rl_process(struct servtab * sep,int ctrl)77 rl_process(struct servtab *sep, int ctrl)
78 {
79 time_t now = -1;
80
81 DPRINTF(SERV_FMT ": processing rate-limiting",
82 SERV_PARAMS(sep));
83 DPRINTF(SERV_FMT ": se_service_max "
84 "%zu and se_count %zu", SERV_PARAMS(sep),
85 sep->se_service_max, sep->se_count);
86
87 if (sep->se_count == 0) {
88 now = rl_time();
89 sep->se_time = now;
90 }
91
92 if (!rl_process_service_max(sep, ctrl, &now)
93 || !rl_process_ip_max(sep, ctrl, &now)) {
94 return -1;
95 }
96
97 DPRINTF(SERV_FMT ": running service ", SERV_PARAMS(sep));
98
99 /* se_count is only incremented if rl_process will return 0 */
100 sep->se_count++;
101 return 0;
102 }
103
104 /*
105 * Get the identifier for the remote peer based on sep->se_socktype and
106 * sep->se_family
107 */
108 static void
rl_get_name(struct servtab * sep,int ctrl,union addr * out)109 rl_get_name(struct servtab *sep, int ctrl, union addr *out)
110 {
111 union {
112 struct sockaddr_storage ss;
113 struct sockaddr sa;
114 struct sockaddr_in sin;
115 struct sockaddr_in6 sin6;
116 } addr;
117
118 /* Get the sockaddr of socket ctrl */
119 switch (sep->se_socktype) {
120 case SOCK_STREAM: {
121 socklen_t len = sizeof(struct sockaddr_storage);
122 if (getpeername(ctrl, &addr.sa, &len) == -1) {
123 /* error, log it and skip ip rate limiting */
124 syslog(LOG_ERR,
125 SERV_FMT " failed to get peer name of the "
126 "connection", SERV_PARAMS(sep));
127 exit(EXIT_FAILURE);
128 }
129 break;
130 }
131 case SOCK_DGRAM: {
132 struct msghdr header = {
133 .msg_name = &addr.sa,
134 .msg_namelen = sizeof(struct sockaddr_storage),
135 /* scatter/gather and control info is null */
136 };
137 ssize_t count;
138
139 /* Peek so service can still get the packet */
140 count = recvmsg(ctrl, &header, MSG_PEEK);
141 if (count == -1) {
142 syslog(LOG_ERR,
143 "failed to get dgram source address: %s; exiting",
144 strerror(errno));
145 exit(EXIT_FAILURE);
146 }
147 break;
148 }
149 default:
150 DPRINTF(SERV_FMT ": ip_max rate limiting not supported for "
151 "socktype", SERV_PARAMS(sep));
152 syslog(LOG_ERR, SERV_FMT
153 ": ip_max rate limiting not supported for socktype",
154 SERV_PARAMS(sep));
155 exit(EXIT_FAILURE);
156 }
157
158 /* Convert addr to to rate limiting address */
159 switch (sep->se_family) {
160 case AF_INET:
161 out->ipv4_addr = addr.sin.sin_addr;
162 break;
163 #ifdef INET6
164 case AF_INET6:
165 out->ipv6_addr = addr.sin6.sin6_addr;
166 break;
167 #endif
168 default: {
169 int res = getnameinfo(&addr.sa,
170 (socklen_t)addr.sa.sa_len,
171 out->other_addr, NI_MAXHOST,
172 NULL, 0,
173 NI_NUMERICHOST
174 );
175 if (res != 0) {
176 syslog(LOG_ERR,
177 SERV_FMT ": failed to get name info of "
178 "the incoming connection: %s; exiting",
179 SERV_PARAMS(sep), gai_strerror(res));
180 exit(EXIT_FAILURE);
181 }
182 break;
183 }
184 }
185 }
186
187 static void
rl_drop_connection(struct servtab * sep,int ctrl)188 rl_drop_connection(struct servtab *sep, int ctrl)
189 {
190
191 if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) {
192 /*
193 * If the fd isn't a listen socket,
194 * close the individual connection too.
195 */
196 close(ctrl);
197 return;
198 }
199 if (sep->se_socktype != SOCK_DGRAM) {
200 return;
201 }
202 /*
203 * Drop the single datagram the service would have
204 * consumed if nowait. If this is a wait service, this
205 * will consume 1 datagram, and further received packets
206 * will be removed in the same way.
207 */
208 struct msghdr header = {
209 /* All fields null, just consume one message */
210 };
211 ssize_t count;
212
213 count = recvmsg(ctrl, &header, 0);
214 if (count == -1) {
215 syslog(LOG_ERR,
216 SERV_FMT ": failed to consume nowait dgram: %s",
217 SERV_PARAMS(sep), strerror(errno));
218 exit(EXIT_FAILURE);
219 }
220 DPRINTF(SERV_FMT ": dropped dgram message",
221 SERV_PARAMS(sep));
222 }
223
224 static time_t
rl_time(void)225 rl_time(void)
226 {
227 struct timespec ts;
228 if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
229 syslog(LOG_ERR, "clock_gettime for rate limiting failed: %s; "
230 "exiting", strerror(errno));
231 /* Exit inetd if rate limiting fails */
232 exit(EXIT_FAILURE);
233 }
234 return ts.tv_sec;
235 }
236
237 /* Add addr to IP tracking or return NULL if malloc fails */
238 static struct rl_ip_node *
rl_add(struct servtab * sep,union addr * addr)239 rl_add(struct servtab *sep, union addr *addr)
240 {
241
242 struct rl_ip_node *node;
243 size_t node_size, bufsize;
244 #ifdef DEBUG_ENABLE
245 char buffer[NI_MAXHOST];
246 #endif
247
248 switch(sep->se_family) {
249 case AF_INET:
250 /* ip_node to end of IPv4 address */
251 node_size = offsetof(struct rl_ip_node, ipv4_addr)
252 + sizeof(struct in_addr);
253 break;
254 case AF_INET6:
255 /* ip_node to end of IPv6 address */
256 node_size = offsetof(struct rl_ip_node, ipv6_addr)
257 + sizeof(struct in6_addr);
258 break;
259 default:
260 /* ip_node to other_addr plus size of string + NULL */
261 bufsize = strlen(addr->other_addr) + sizeof(char);
262 node_size = offsetof(struct rl_ip_node, other_addr) + bufsize;
263 break;
264 }
265
266 node = malloc(node_size);
267 if (node == NULL) {
268 if (errno == ENOMEM) {
269 return NULL;
270 } else {
271 syslog(LOG_ERR, "malloc failed unexpectedly: %s",
272 strerror(errno));
273 exit(EXIT_FAILURE);
274 }
275 }
276
277 node->count = 0;
278
279 /* copy the data into the new allocation */
280 switch(sep->se_family) {
281 case AF_INET:
282 node->ipv4_addr = addr->ipv4_addr;
283 break;
284 case AF_INET6:
285 /* Hopefully this is inlined, means the same thing as memcpy */
286 __builtin_memcpy(&node->ipv6_addr, &addr->ipv6_addr,
287 sizeof(struct in6_addr));
288 break;
289 default:
290 strlcpy(node->other_addr, addr->other_addr, bufsize);
291 break;
292 }
293
294 /* initializes 'entries' member to NULL automatically */
295 SLIST_INSERT_HEAD(&sep->se_rl_ip_list, node, entries);
296
297 DPRINTF(SERV_FMT ": add '%s' to rate limit tracking (%zu byte record)",
298 SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer), node_size);
299
300 return node;
301 }
302
303 static void
rl_reset(struct servtab * sep,time_t now)304 rl_reset(struct servtab *sep, time_t now)
305 {
306 DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ",
307 SERV_PARAMS(sep), (intmax_t)(now - sep->se_time));
308
309 sep->se_count = 0;
310 sep->se_time = now;
311 if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
312 rl_clear_ip_list(sep);
313 }
314 }
315
316 void
rl_clear_ip_list(struct servtab * sep)317 rl_clear_ip_list(struct servtab *sep)
318 {
319 while (!SLIST_EMPTY(&sep->se_rl_ip_list)) {
320 struct rl_ip_node *node = SLIST_FIRST(&sep->se_rl_ip_list);
321 SLIST_REMOVE_HEAD(&sep->se_rl_ip_list, entries);
322 free(node);
323 }
324 }
325
326 /* Get the node associated with addr, or NULL */
327 static struct rl_ip_node *
rl_try_get_ip(struct servtab * sep,union addr * addr)328 rl_try_get_ip(struct servtab *sep, union addr *addr)
329 {
330
331 struct rl_ip_node *cur;
332 SLIST_FOREACH(cur, &sep->se_rl_ip_list, entries) {
333 if (rl_ip_eq(sep, addr, cur)) {
334 return cur;
335 }
336 }
337
338 return NULL;
339 }
340
341 /* Return true if passed service rate limiting checks, false if blocked */
342 static bool
rl_process_service_max(struct servtab * sep,int ctrl,time_t * now)343 rl_process_service_max(struct servtab *sep, int ctrl, time_t *now)
344 {
345 if (sep->se_count >= sep->se_service_max) {
346 if (*now == -1) {
347 /* Only get the clock time if we didn't already */
348 *now = rl_time();
349 }
350
351 if (*now - sep->se_time > CNT_INTVL) {
352 rl_reset(sep, *now);
353 } else {
354 syslog(LOG_ERR, SERV_FMT
355 ": max spawn rate (%zu in %ji seconds) "
356 "already met; closing for %ju seconds",
357 SERV_PARAMS(sep),
358 sep->se_service_max,
359 (intmax_t)CNT_INTVL,
360 (uintmax_t)RETRYTIME);
361 DPRINTF(SERV_FMT
362 ": max spawn rate (%zu in %ji seconds) "
363 "already met; closing for %ju seconds",
364 SERV_PARAMS(sep),
365 sep->se_service_max,
366 (intmax_t)CNT_INTVL,
367 (uintmax_t)RETRYTIME);
368
369 rl_drop_connection(sep, ctrl);
370
371 /* Close the server for 10 minutes */
372 close_sep(sep);
373 if (!timingout) {
374 timingout = true;
375 alarm(RETRYTIME);
376 }
377
378 return false;
379 }
380 }
381 return true;
382 }
383
384 /* Return true if passed IP rate limiting checks, false if blocked */
385 static bool
rl_process_ip_max(struct servtab * sep,int ctrl,time_t * now)386 rl_process_ip_max(struct servtab *sep, int ctrl, time_t *now) {
387 if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) {
388 struct rl_ip_node *node;
389 union addr addr;
390
391 rl_get_name(sep, ctrl, &addr);
392 node = rl_try_get_ip(sep, &addr);
393 if (node == NULL) {
394 node = rl_add(sep, &addr);
395 if (node == NULL) {
396 /* If rl_add can't allocate, reject request */
397 DPRINTF("Cannot allocate rl_ip_node");
398 return false;
399 }
400 }
401 #ifdef DEBUG_ENABLE
402 else {
403 /*
404 * in a separate function to prevent large stack
405 * frame
406 */
407 rl_print_found_node(sep, node);
408 }
409 #endif
410
411 DPRINTF(
412 SERV_FMT ": se_ip_max %zu and ip_count %zu",
413 SERV_PARAMS(sep), sep->se_ip_max, node->count);
414
415 if (node->count >= sep->se_ip_max) {
416 if (*now == -1) {
417 *now = rl_time();
418 }
419
420 if (*now - sep->se_time > CNT_INTVL) {
421 rl_reset(sep, *now);
422 node = rl_add(sep, &addr);
423 if (node == NULL) {
424 DPRINTF("Cannot allocate rl_ip_node");
425 return false;
426 }
427 } else {
428 if (debug && node->count == sep->se_ip_max) {
429 /*
430 * Only log first failed request to
431 * prevent DoS attack writing to system
432 * log
433 */
434 rl_log_address_exceed(sep, node);
435 } else {
436 DPRINTF(SERV_FMT
437 ": service not started",
438 SERV_PARAMS(sep));
439 }
440
441 rl_drop_connection(sep, ctrl);
442 /*
443 * Increment so debug-syslog message will
444 * trigger only once
445 */
446 if (node->count < SIZE_MAX) {
447 node->count++;
448 }
449 return false;
450 }
451 }
452 node->count++;
453 }
454 return true;
455 }
456
457 static bool
rl_ip_eq(struct servtab * sep,union addr * addr,struct rl_ip_node * cur)458 rl_ip_eq(struct servtab *sep, union addr *addr, struct rl_ip_node *cur) {
459 switch(sep->se_family) {
460 case AF_INET:
461 if (addr->ipv4_addr.s_addr == cur->ipv4_addr.s_addr) {
462 return true;
463 }
464 break;
465 #ifdef INET6
466 case AF_INET6:
467 if (rl_ipv6_eq(&addr->ipv6_addr, &cur->ipv6_addr)) {
468 return true;
469 }
470 break;
471 #endif
472 default:
473 if (strncmp(cur->other_addr, addr->other_addr, NI_MAXHOST)
474 == 0) {
475 return true;
476 }
477 break;
478 }
479 return false;
480 }
481
482 #ifdef INET6
483 static bool
rl_ipv6_eq(struct in6_addr * a,struct in6_addr * b)484 rl_ipv6_eq(struct in6_addr *a, struct in6_addr *b)
485 {
486 #if UINTMAX_MAX >= UINT64_MAX
487 { /* requires 8 byte aligned structs */
488 uint64_t *ap = (uint64_t *)a->s6_addr;
489 uint64_t *bp = (uint64_t *)b->s6_addr;
490 return (ap[0] == bp[0]) & (ap[1] == bp[1]);
491 }
492 #else
493 { /* requires 4 byte aligned structs */
494 uint32_t *ap = (uint32_t *)a->s6_addr;
495 uint32_t *bp = (uint32_t *)b->s6_addr;
496 return ap[0] == bp[0] && ap[1] == bp[1] &&
497 ap[2] == bp[2] && ap[3] == bp[3];
498 }
499 #endif
500 }
501 #endif
502
503 static const char *
rl_node_tostring(struct servtab * sep,struct rl_ip_node * node,char buffer[NI_MAXHOST])504 rl_node_tostring(struct servtab *sep, struct rl_ip_node *node,
505 char buffer[NI_MAXHOST])
506 {
507 switch (sep->se_family) {
508 case AF_INET:
509 #ifdef INET6
510 case AF_INET6:
511 #endif
512 /* ipv4_addr/ipv6_addr share same address */
513 return inet_ntop(sep->se_family, (void*)&node->ipv4_addr,
514 (char*)buffer, NI_MAXHOST);
515 default:
516 return (char *)&node->other_addr;
517 }
518 }
519
520 #ifdef DEBUG_ENABLE
521 /* Separate function due to large buffer size */
522 static void
rl_print_found_node(struct servtab * sep,struct rl_ip_node * node)523 rl_print_found_node(struct servtab *sep, struct rl_ip_node *node)
524 {
525 char buffer[NI_MAXHOST];
526 DPRINTF(SERV_FMT ": found record for address '%s'",
527 SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer));
528 }
529 #endif
530
531 /* Separate function due to large buffer sie */
532 static void
rl_log_address_exceed(struct servtab * sep,struct rl_ip_node * node)533 rl_log_address_exceed(struct servtab *sep, struct rl_ip_node *node)
534 {
535 char buffer[NI_MAXHOST];
536 const char * name = rl_node_tostring(sep, node, buffer);
537 syslog(LOG_ERR, SERV_FMT
538 ": max ip spawn rate (%zu in "
539 "%ji seconds) for "
540 "'%." TOSTRING(NI_MAXHOST) "s' "
541 "already met; service not started",
542 SERV_PARAMS(sep),
543 sep->se_ip_max,
544 (intmax_t)CNT_INTVL,
545 name);
546 DPRINTF(SERV_FMT
547 ": max ip spawn rate (%zu in "
548 "%ji seconds) for "
549 "'%." TOSTRING(NI_MAXHOST) "s' "
550 "already met; service not started",
551 SERV_PARAMS(sep),
552 sep->se_ip_max,
553 (intmax_t)CNT_INTVL,
554 name);
555 }
556