1 /* $NetBSD: ratelimit.c,v 1.4 2025/12/24 17:54:17 thorpej 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.4 2025/12/24 17:54:17 thorpej 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 #ifdef INET6 256 case AF_INET6: 257 /* ip_node to end of IPv6 address */ 258 node_size = offsetof(struct rl_ip_node, ipv6_addr) 259 + sizeof(struct in6_addr); 260 break; 261 #endif 262 default: 263 /* ip_node to other_addr plus size of string + NULL */ 264 bufsize = strlen(addr->other_addr) + sizeof(char); 265 node_size = offsetof(struct rl_ip_node, other_addr) + bufsize; 266 break; 267 } 268 269 node_size = MAX(node_size, sizeof *node); 270 271 node = malloc(node_size); 272 if (node == NULL) { 273 if (errno == ENOMEM) { 274 return NULL; 275 } else { 276 syslog(LOG_ERR, "malloc failed unexpectedly: %s", 277 strerror(errno)); 278 exit(EXIT_FAILURE); 279 } 280 } 281 282 node->count = 0; 283 284 /* copy the data into the new allocation */ 285 switch(sep->se_family) { 286 case AF_INET: 287 node->ipv4_addr = addr->ipv4_addr; 288 break; 289 #ifdef INET6 290 case AF_INET6: 291 /* Hopefully this is inlined, means the same thing as memcpy */ 292 __builtin_memcpy(&node->ipv6_addr, &addr->ipv6_addr, 293 sizeof(struct in6_addr)); 294 break; 295 #endif 296 default: 297 strlcpy(node->other_addr, addr->other_addr, bufsize); 298 break; 299 } 300 301 /* initializes 'entries' member to NULL automatically */ 302 SLIST_INSERT_HEAD(&sep->se_rl_ip_list, node, entries); 303 304 DPRINTF(SERV_FMT ": add '%s' to rate limit tracking (%zu byte record)", 305 SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer), node_size); 306 307 return node; 308 } 309 310 static void 311 rl_reset(struct servtab *sep, time_t now) 312 { 313 DPRINTF(SERV_FMT ": %ji seconds passed; resetting rate limiting ", 314 SERV_PARAMS(sep), (intmax_t)(now - sep->se_time)); 315 316 sep->se_count = 0; 317 sep->se_time = now; 318 if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) { 319 rl_clear_ip_list(sep); 320 } 321 } 322 323 void 324 rl_clear_ip_list(struct servtab *sep) 325 { 326 while (!SLIST_EMPTY(&sep->se_rl_ip_list)) { 327 struct rl_ip_node *node = SLIST_FIRST(&sep->se_rl_ip_list); 328 SLIST_REMOVE_HEAD(&sep->se_rl_ip_list, entries); 329 free(node); 330 } 331 } 332 333 /* Get the node associated with addr, or NULL */ 334 static struct rl_ip_node * 335 rl_try_get_ip(struct servtab *sep, union addr *addr) 336 { 337 338 struct rl_ip_node *cur; 339 SLIST_FOREACH(cur, &sep->se_rl_ip_list, entries) { 340 if (rl_ip_eq(sep, addr, cur)) { 341 return cur; 342 } 343 } 344 345 return NULL; 346 } 347 348 /* Return true if passed service rate limiting checks, false if blocked */ 349 static bool 350 rl_process_service_max(struct servtab *sep, int ctrl, time_t *now) 351 { 352 if (sep->se_count >= sep->se_service_max) { 353 if (*now == -1) { 354 /* Only get the clock time if we didn't already */ 355 *now = rl_time(); 356 } 357 358 if (*now - sep->se_time > CNT_INTVL) { 359 rl_reset(sep, *now); 360 } else { 361 syslog(LOG_ERR, 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 DPRINTF(SERV_FMT 369 ": max spawn rate (%zu in %ji seconds) " 370 "already met; closing for %ju seconds", 371 SERV_PARAMS(sep), 372 sep->se_service_max, 373 (intmax_t)CNT_INTVL, 374 (uintmax_t)RETRYTIME); 375 376 rl_drop_connection(sep, ctrl); 377 378 /* Close the server for 10 minutes */ 379 close_sep(sep); 380 if (!timingout) { 381 timingout = true; 382 alarm(RETRYTIME); 383 } 384 385 return false; 386 } 387 } 388 return true; 389 } 390 391 /* Return true if passed IP rate limiting checks, false if blocked */ 392 static bool 393 rl_process_ip_max(struct servtab *sep, int ctrl, time_t *now) { 394 if (sep->se_ip_max != SERVTAB_UNSPEC_SIZE_T) { 395 struct rl_ip_node *node; 396 union addr addr; 397 398 rl_get_name(sep, ctrl, &addr); 399 node = rl_try_get_ip(sep, &addr); 400 if (node == NULL) { 401 node = rl_add(sep, &addr); 402 if (node == NULL) { 403 /* If rl_add can't allocate, reject request */ 404 DPRINTF("Cannot allocate rl_ip_node"); 405 return false; 406 } 407 } 408 #ifdef DEBUG_ENABLE 409 else { 410 /* 411 * in a separate function to prevent large stack 412 * frame 413 */ 414 rl_print_found_node(sep, node); 415 } 416 #endif 417 418 DPRINTF( 419 SERV_FMT ": se_ip_max %zu and ip_count %zu", 420 SERV_PARAMS(sep), sep->se_ip_max, node->count); 421 422 if (node->count >= sep->se_ip_max) { 423 if (*now == -1) { 424 *now = rl_time(); 425 } 426 427 if (*now - sep->se_time > CNT_INTVL) { 428 rl_reset(sep, *now); 429 node = rl_add(sep, &addr); 430 if (node == NULL) { 431 DPRINTF("Cannot allocate rl_ip_node"); 432 return false; 433 } 434 } else { 435 if (debug && node->count == sep->se_ip_max) { 436 /* 437 * Only log first failed request to 438 * prevent DoS attack writing to system 439 * log 440 */ 441 rl_log_address_exceed(sep, node); 442 } else { 443 DPRINTF(SERV_FMT 444 ": service not started", 445 SERV_PARAMS(sep)); 446 } 447 448 rl_drop_connection(sep, ctrl); 449 /* 450 * Increment so debug-syslog message will 451 * trigger only once 452 */ 453 if (node->count < SIZE_MAX) { 454 node->count++; 455 } 456 return false; 457 } 458 } 459 node->count++; 460 } 461 return true; 462 } 463 464 static bool 465 rl_ip_eq(struct servtab *sep, union addr *addr, struct rl_ip_node *cur) { 466 switch(sep->se_family) { 467 case AF_INET: 468 if (addr->ipv4_addr.s_addr == cur->ipv4_addr.s_addr) { 469 return true; 470 } 471 break; 472 #ifdef INET6 473 case AF_INET6: 474 if (rl_ipv6_eq(&addr->ipv6_addr, &cur->ipv6_addr)) { 475 return true; 476 } 477 break; 478 #endif 479 default: 480 if (strncmp(cur->other_addr, addr->other_addr, NI_MAXHOST) 481 == 0) { 482 return true; 483 } 484 break; 485 } 486 return false; 487 } 488 489 #ifdef INET6 490 static bool 491 rl_ipv6_eq(struct in6_addr *a, struct in6_addr *b) 492 { 493 #if UINTMAX_MAX >= UINT64_MAX 494 { /* requires 8 byte aligned structs */ 495 uint64_t *ap = (uint64_t *)a->s6_addr; 496 uint64_t *bp = (uint64_t *)b->s6_addr; 497 return (ap[0] == bp[0]) & (ap[1] == bp[1]); 498 } 499 #else 500 { /* requires 4 byte aligned structs */ 501 uint32_t *ap = (uint32_t *)a->s6_addr; 502 uint32_t *bp = (uint32_t *)b->s6_addr; 503 return ap[0] == bp[0] && ap[1] == bp[1] && 504 ap[2] == bp[2] && ap[3] == bp[3]; 505 } 506 #endif 507 } 508 #endif 509 510 static const char * 511 rl_node_tostring(struct servtab *sep, struct rl_ip_node *node, 512 char buffer[NI_MAXHOST]) 513 { 514 switch (sep->se_family) { 515 case AF_INET: 516 #ifdef INET6 517 case AF_INET6: 518 #endif 519 /* ipv4_addr/ipv6_addr share same address */ 520 return inet_ntop(sep->se_family, (void*)&node->ipv4_addr, 521 (char*)buffer, NI_MAXHOST); 522 default: 523 return (char *)&node->other_addr; 524 } 525 } 526 527 #ifdef DEBUG_ENABLE 528 /* Separate function due to large buffer size */ 529 static void 530 rl_print_found_node(struct servtab *sep, struct rl_ip_node *node) 531 { 532 char buffer[NI_MAXHOST]; 533 DPRINTF(SERV_FMT ": found record for address '%s'", 534 SERV_PARAMS(sep), rl_node_tostring(sep, node, buffer)); 535 } 536 #endif 537 538 /* Separate function due to large buffer sie */ 539 static void 540 rl_log_address_exceed(struct servtab *sep, struct rl_ip_node *node) 541 { 542 char buffer[NI_MAXHOST]; 543 const char * name = rl_node_tostring(sep, node, buffer); 544 syslog(LOG_ERR, SERV_FMT 545 ": max ip spawn rate (%zu in " 546 "%ji seconds) for " 547 "'%." TOSTRING(NI_MAXHOST) "s' " 548 "already met; service not started", 549 SERV_PARAMS(sep), 550 sep->se_ip_max, 551 (intmax_t)CNT_INTVL, 552 name); 553 DPRINTF(SERV_FMT 554 ": max ip spawn rate (%zu in " 555 "%ji seconds) for " 556 "'%." TOSTRING(NI_MAXHOST) "s' " 557 "already met; service not started", 558 SERV_PARAMS(sep), 559 sep->se_ip_max, 560 (intmax_t)CNT_INTVL, 561 name); 562 } 563