Home | History | Annotate | Line # | Download | only in inetd
ratelimit.c revision 1.1
      1 /*	$NetBSD: ratelimit.c,v 1.1 2021/10/12 19:08:04 christos 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.1 2021/10/12 19:08:04 christos 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
     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
    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
    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
    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 *
    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
    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
    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 *
    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
    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
    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
    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
    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 *
    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
    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
    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