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